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-scopedkvick_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-apimarkersnpm run predeploychains 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-apimarkers 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_idcolumn 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:
- They are the historical record — anyone reading the migration sequence sees "added, then dropped, with full context in each file's header"
- Replaying the migrations on a fresh D1 produces the same final state (the drop neutralizes the add)
- The header in
040_drop_kvick_operator_identity.sqlcross-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
- Preflight (pre-deploy audit) — the replacement toolchain
- Security model — why a second auth surface was the load-bearing concern
- ADR-0026: Google OAuth + device sessions — the per-shop auth surface this would have layered on top of