Skip to content

Flip readLegacyHeaders from warn-and-accept to a loud dated rejection — step 3 of the #9930 retirement #10164

Description

@os-zhuang

Blocked-by: #9930 (retired 2026-08-20 — #9930 CLOSED; see Hold state below)

Step 3 of the maintainer-ruled retirement recorded on #9930 (ruling comment 5353921673, 2026-08-20, 「其他接受你的建议。」): flip the legacy cleartext read path from warn-and-accept to a loud, dated rejection (enforce-or-remove). Deliberately not implemented in the #9930 dispatch — filed here per that dispatch's instruction.

The surface (anchor by symbol, not line)

  • packages/plugins/plugin-webhooks/src/auto-enqueuer.tsAutoEnqueuer.attachHeaders: after resolveWebhookHeaders finds nothing stored, it calls readLegacyHeaders(row?.definition_json) and, when a legacy map is present, warns (CLEARTEXT in definition_json … #7986) and honors it (sub.headers = legacy).
  • packages/plugins/plugin-webhooks/src/webhook-headers.tsreadLegacyHeaders itself.
  • Sibling to consider in the same stroke or as its own card: readLegacySecret in webhook-secret.ts has the identical warn-and-accept shape one block above (auto-enqueuer.ts, attachSecret path, [security] The webhook signing secret is stored in cleartext in sys_webhook.definition_json #7799) — rejecting one passenger while accepting the other leaves half the column alive.

Real-world precondition (why this waits, measured 2026-08-20)

The ruling's own precondition is "once the sweep has run" — and the sweep runs at deployment boot, not at merge. Shipping the rejection before a deployment has booted the sweep turns a working delivery into a hard failure on exactly the rows the migration rescues.

Measured state of the sweep (from the #9930 step-1 census):

So the precondition in practice: every deployment class this project supports must have booted a release carrying PR #8114's sweep (with a CryptoProvider wired) before the rejection ships. Record the date chosen and why in the rejection message.

Remaining production channel to close in the same stroke

In-repo producers of definition_json.headers are zero (census on #9930: the seeder splits headers into headers_secret; docs fixed by PR #10067). The one channel left open: a raw data-API write (PATCH /api/v1/data/sys_webhook) can still store a definition_json string whose content embeds a headers (or secret) key — nothing at the write door parses the blob's content (the #8566 gate judges only the headers_secret column's shape). Today such a row is honored-with-warn until the next boot sweeps it. When the read path flips to rejection, that write becomes a delayed hard failure — so consider rejecting the legacy shape at the write door in the same change, where the author is still standing (the #8566 pattern).

Acceptance shape

  • Rejection tests assert the ADR-0112 envelope (code + status), not bare toThrow.
  • The rejection message is dated, names the sweep (migrateLegacyWebhookSecrets) and the remedy, per the loud-absence rule.
  • Changeset: user-visible behavioral change (legacy rows stop delivering) — breaking-changeset discipline applies, including the ADR-0087 disposition marker.

Refs: #9930 (ruling + census) · #7986 · PR #8114 (the sweep's headers half) · #8566 (write-door gate pattern) · #7799.


Generated by Claude Code


Hold state

Appended 2026-08-20 by the triage seat (session session_01JTMsq2Gv3qDWMcGWKi6Sb8). Everything above is the filing seat's text, unmodified except the first line, whose card-dependency is retired: #9930 closed 2026-08-20T12:29Z, so this card no longer waits on a card — what remains is the ruling's own deployment-boot precondition plus the breaking-train window, which is a condition, hence pm:blockedpm:on-hold. The H9 predicate reads issue.body only, so the exit lives here.

Restart-when: a v18 release-candidate tag exists on this repo (git ls-remote --tags origin shows a v18*-rc* tag) — the ADR-0087 breaking window this rejection rides. At that point re-verify the deployment-boot precondition above (has every supported deployment class booted a release at or after PR #8114, with a CryptoProvider wired?) and return the card to the queue.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions