docs(adr): ADR-0128 — producer-discriminated AAD for CryptoContext (direction only, not implemented) - #12839
Conversation
… intended direction (ADR-0128) Records direction B from the maintainer ruling of 2026-08-27 on #12599: the AAD binding on `CryptoContext` should be producer-discriminated (a required scope discriminant naming which of the three vocabularies `(namespace, key)` is drawn from) with a delimiter-safe encoding, fixed at the producer rather than tolerated downstream. The record carries the measured hazard (the `mail|api_key` cross-vocabulary construction, and the four in-tree facts that compose it), the two costs that defer it (a breaking change to the published `ICryptoProvider` export plus an at-rest rewrap of every existing ciphertext), and four triggers that would fund the work. Option C (reserve-and-reject colliding object names) is recorded as considered-and-declined. Zero behaviour change: nothing in the decision is implemented and no card is opened by this record. The prose half of the same ruling ships separately. Co-authored-by: Claude <noreply@anthropic.com>
The second funding trigger in the record's own terms read "A second producer
of `CryptoContext` is added", contradicting §1.1 (three producers, measured)
and the very next sentence of the same bullet ("A fourth vocabulary in a flat
space is the same defect with more surface").
One word. No other change to the record, and no change to the decision.
Co-authored-by: Claude <noreply@anthropic.com>
Governed-merge audit reading — recording it here so the record lives where the merge didTriage seat, session What the audit read
⭐ The MCP channel could, so the column is no longer blank: Why it is being surfaced rather than passed overThis PR's own body sets its landing discipline, verbatim:
And ⇒ Three readings are all consistent with the facts, and ⛔ I cannot distinguish them from here:
The one questionDoes the maintainer recognise this merge? That is the audit's whole criterion ("An entry the maintainer does not recognise is the violation signal"), and it is not a question a seat can answer for them. ⛔ Until it is answered: no incident is filed, no seat is named as violating, and nothing about this PR or its commit is touched. If the answer is "not mine", I file under the #9495 regime with the full readings attached. If it is "mine / I ordered it", this is closed and ⛔ not re-reported. Method note, useful beyond this PRThe audit script reports Full round record: #6015 comment Generated by Claude Code |
Part of #12599
⛔ Not
Fixes, deliberately. The card's terminal state belongs to the prose PR #12838, not to this one. This PR is the governed face of the same ruling and waits on the maintainer's hand-merge, which is itself the acceptance act (Prime Directive #14). Stays draft — the PM handles its terminal state; do not flip it ready.Authored in Claude Code session
session_01JvjTCjJQn9zSTXEhUKgT7s(recorded here in prose so it survives any later body edit).What this is
Option B of the maintainer ruling of 2026-08-27 on #12599 (comment 5434987411, verbatim 「其他同意」), recorded as direction and not implemented: producer-discriminated AAD for
CryptoContext— a required scope discriminant naming which of the three vocabularies(namespace, key)is drawn from, plus a delimiter-safe encoding, fixed at the producer rather than tolerated downstream.One new file:
docs/adr/0128-producer-discriminated-aad-for-cryptocontext.md. Zero behaviour change, zero code touched, no card opened by it.What the record carries, per the ruling
mail|api_keycross-vocabulary construction, with the four in-tree facts that compose it: one shared provider instance across all three producers;sys_secretdeclaring(namespace, key)non-unique by design; nothing reserving the colliding names; and the colliding pair already existing on the settings side (the provider's own AAD pin test is literally written on{ namespace: 'mail', key: 'api_key' }). Measured live-instance count: zero — no intersection between the shipped settings namespaces and the objects carrying aField.secret()field. That zero is the whole of the deferral's evidence, and the record says so.ICryptoProviderexport, plus an at-rest rewrap of every existing ciphertext (each was sealed under the old AAD, so every one fails to decrypt until re-wrapped). §4 also names the ordering constraint that makes this a project rather than a patch: the migration must read under the old derivation and write under the new one, so both must coexist and each ciphertext must know which sealed it.CryptoContextis added;ICryptoProvideris opened for another breaking change anyway; or a compliance/customer requirement asks the platform to state its at-rest ciphertext binding.One thing the record adds beyond the ruling's list, disclosed without an obligation attached: the AAD's separator is an unescaped
|, so the encoding is ambiguous even within one vocabulary. Measured at zero producers, unreachable from object and field names, reachable in principle from a settings specifier key or a caller-supplied datasource namespace. It is why D1 (discriminant) and D2 (encoding) are one change — a third component on an ambiguous join makes the ambiguity worse.Relationship to #12838
Independent branches, green in either landing order. The ADR cites
crypto-provider.ts; the prose PR deliberately does not citeADR-0128by number, becausecheck:adr-anchorshard-fails a citation naming no record underdocs/adr/— so a citation from the prose PR would sit red onmainfor as long as this draft waits. The docblock names the intended end state in prose instead.0128re-checked as free at this branch's base (f907fbe9) and again after the prose branch's merge oforigin/main; latest existing record is0127.Verification
Gate set derived by
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackon the actual changed set (1 path). All five derived families green atd9aa00a2, exit codes captured before any pipe:pnpm check:adr-anchors— exit 0 (filename convention, ADR-number uniqueness, and the repo-wide citation scan that resolves everyADR-NNNNin tracked text)node scripts/check-adr-links.mjs— exit 0 (every relative link in the record resolves; the record links only ADR-0049, ADR-0087, ADR-0100 and ADR-0112, all present)pnpm check:doc-authoring— exit 0pnpm check:pm-governed-merges— exit 0pnpm --filter @objectstack/lint run check:doc-formula-expressions— exit 0 (after building@objectstack/formulaand@objectstack/lint; on an unbuilt workspace this gate exits 1 withPREREQUISITE NOT METand measures nothing)Also green:
pnpm check:nul-bytesandpnpm check:docs-single-h1.Generated by Claude Code