Skip to main content

ADR-0027 — Local preflight over a remote Kvick operator identity

  • Status: Accepted
  • Date: 2026-05-19
  • Decision-makers: Tom Anderson
  • Supersedes: the un-shipped "Kvick operator identity" spec (migrations 039 → 040 add-then-revert)

Context

The original spec called for a parallel identity layer so Tom (and future Kvick employees) could sign into any shop's Helm deployment as Kvick, distinct from the shop's own staff. The proposed schema (migration 039_kvick_operator_identity.sql):

  • kvick_operators — the people (email + long-lived API token)
  • kvick_operator_sessions — sliding 12-hour sessions, cookie-scoped
  • kvick_test_runs — local mirror of Sys Admin Worker run state (Part 3 of the spec)
  • audit_events.actor_kvick_operator_id — new column so audit queries could distinguish "what did Kvick do here" from "what did the shop do here"

The motivating use cases were:

  • Running a sys-admin test run against a live shop deployment without touching shop staff records
  • Tracking which Kvick employee took which action across all deployed shops
  • Long-lived API tokens for tooling that needs to hit the live Worker

After review, the real day-to-day need turned out to be catching code rot before each deploy, not "Kvick operators signing into shops." Spec'd-but-not-shipped Kvick-operator-login surfaces would have:

  • Added a second auth surface on every shop's Worker (more attack surface)
  • Introduced cross-shop credentials (a stolen Kvick token could touch every shop)
  • Required ongoing maintenance of a credential rotation flow + revocation UI
  • Delivered marginal benefit over wrangler tail + the existing audit chain for forensic investigations

Meanwhile a local script that walks src/ + public/ + migrations/ and reports six categories of rot delivered the bulk of the perceived value (preventing dead-code accumulation, catching schema drift, surfacing orphan routes) at zero new attack surface, zero new credentials, and zero ongoing rotation burden.

Decision

Drop the kvick_operator identity layer (migration 040) and replace it with a local preflight toolchain.

The replacement, described in the preflight contributing doc:

  • scripts/preflight.js — six structured checks (route hygiene, dead exports, schema drift, shadow code, commented-out code, file bloat)
  • scripts/preflight-autofix.js — opinionated safe deletions, gated by @public-api markers
  • npm run predeploy chains autofix → audit → wrangler deploy
  • Lock files in public/_preflight.json + public/_preflight_changes.json
  • Two in-product surfaces (QA dropdown + "📝 Last sweep" modal) consume the lock files

When a future need genuinely requires Kvick-employee identity at the Worker layer (likely candidates: a tools-only sub-domain for Kvick-internal automation; a chain-management UI that spans multiple shop deployments), it gets revisited with a fresh ADR. The earlier spec is preserved as a reference point but should not be re-committed verbatim — the threat model + scope have moved.

Consequences

Positive:

  • No new auth surface on the per-shop Worker → smaller blast radius
  • No cross-shop credentials → a stolen Kvick token can't sweep the fleet
  • Code hygiene runs every deploy automatically; no remote work involved
  • Lock files are checked in, so the audit history of "what was swept when" lives in git
  • @public-api markers create a forcing function to document why a top-level export exists
  • Toolchain is vertical-agnostic (drops into any KVICK.* repo) → first-class platform asset

Negative:

  • "What did Kvick do on shop X" forensic questions still answer through the standard audit chain + wrangler tail; no first-class "actor = Kvick" projection. Acceptable; the audit chain captures the request_id, IP, and timing, and Kvick-initiated requests are rare and identifiable.
  • The vestigial audit_events.actor_kvick_operator_id column lingers in the schema (SQLite can't drop columns without a full table rebuild). Documented as "no-op vestigial; drops at the next audit_events rebuild."
  • A future genuine need for chain-spanning identity will have to re-derive the design.

Notes

The 039-then-040 add-then-revert is intentional documentation. The migration files are kept (not deleted) because:

  1. They are the historical record — anyone reading the migration sequence sees "added, then dropped, with full context in each file's header"
  2. Replaying the migrations on a fresh D1 produces the same final state (the drop neutralizes the add)
  3. The header in 040_drop_kvick_operator_identity.sql cross-references this ADR for the why

This is the bible's standard pattern for reverted infrastructure: the migration files speak, the ADR explains.

See also