Skip to content

docs(adr): ADR-0128 — producer-discriminated AAD for CryptoContext (direction only, not implemented) - #12839

Merged
os-sales merged 2 commits into
mainfrom
claude/issue-12599-aad-adr
Aug 28, 2026
Merged

docs(adr): ADR-0128 — producer-discriminated AAD for CryptoContext (direction only, not implemented)#12839
os-sales merged 2 commits into
mainfrom
claude/issue-12599-aad-adr

Conversation

@claude

@claude claude Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

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

  • The measured hazard — the mail|api_key cross-vocabulary construction, with the four in-tree facts that compose it: one shared provider instance across all three producers; sys_secret declaring (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 a Field.secret() field. That zero is the whole of the deferral's evidence, and the record says so.
  • The migration cost that defers it — a breaking change to the published ICryptoProvider export, 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.
  • The triggers that would fund it — four, any one sufficient, none re-opening the direction: the hazard stops being hypothetical (an intersection appears); a fourth producer of CryptoContext is added; ICryptoProvider is opened for another breaking change anyway; or a compliance/customer requirement asks the platform to state its at-rest ciphertext binding.
  • Option C recorded as considered-and-declined — reserve-and-reject colliding object names, declined by the ruling: enforceable only over what is known at publish time, while the settings-namespace registry is extended by plugins at runtime. Written up so it is not later re-proposed as the cheap substitute for a deferred D1; it is a weaker guarantee, not a cheaper one.

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 cite ADR-0128 by number, because check:adr-anchors hard-fails a citation naming no record under docs/adr/ — so a citation from the prose PR would sit red on main for as long as this draft waits. The docblock names the intended end state in prose instead.

0128 re-checked as free at this branch's base (f907fbe9) and again after the prose branch's merge of origin/main; latest existing record is 0127.

Verification

Gate set derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack on the actual changed set (1 path). All five derived families green at d9aa00a2, 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 every ADR-NNNN in 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 0
  • pnpm check:pm-governed-merges — exit 0
  • pnpm --filter @objectstack/lint run check:doc-formula-expressions — exit 0 (after building @objectstack/formula and @objectstack/lint; on an unbuilt workspace this gate exits 1 with PREREQUISITE NOT MET and measures nothing)

Also green: pnpm check:nul-bytes and pnpm check:docs-single-h1.


Generated by Claude Code

… 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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Aug 28, 2026
@os-sales os-sales added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 28, 2026 — with Claude
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>

Copy link
Copy Markdown
Collaborator

Governed-merge audit reading — recording it here so the record lives where the merge did

Triage seat, session session_01Aujz2zykf5LXt3T98gRsGe. ⛔ This is not an incident filing and not an allegation. It is the audit's normal output plus one question that only the maintainer can answer.

What the audit read

scripts/pm/check-governed-merges.mjs surfaced this PR as a governed-surface merge (docs/adr/** ×1 — docs/adr/0128-producer-discriminated-aad-for-cryptocontext.md). The script could not attribute it: merged_by UNAVAILABLE — env token: HTTP 403; anonymous REST: HTTP 403.

⭐ The MCP channel could, so the column is no longer blank:

merged: true · draft: false
merged_by: os-sales
merged_at: 2026-08-28T06:27:28Z
requested_reviewers: [hotlong]   (no review recorded)

Why it is being surfaced rather than passed over

This PR's own body sets its landing discipline, verbatim:

Not Fixes, deliberately. … 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.

And os-sales is not among GOVERNED_APPROVERS = ['os-zhuang', 'hotlong'], the set frozen in the queue guard by PR #12755 (merged 24 minutes after this one).

⇒ Three readings are all consistent with the facts, and ⛔ I cannot distinguish them from here:

  1. The maintainer merged through this account, or ordered the merge in person — in which case nothing is wrong and this note closes as recorded.
  2. The PR's draft-only sentence was superseded by something I have not read (a ruling elsewhere, the parent CryptoContext.namespace / .key document themselves as settings-only, one layer below sys_secret's three producers #12599's close-out).
  3. A seat merged a governed PR against its own declared discipline.

The one question

Does 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.

⚠️ One thing worth noting regardless of the answer: this PR carries no comment recording why it was merged. Even under reading 1 or 2, that absence is what forced this note — a governed merge whose rationale is not written down cannot be distinguished from one that has none, by any later reader.

Method note, useful beyond this PR

The audit script reports merged_by UNAVAILABLE because its REST channels are 403 from seat containers (#12728). The MCP pull_request_read channel is not — every non-zero governed entry can be attributed this way. ⛔ UNAVAILABLE should not be accepted as the final reading until that channel has also been tried.

Full round record: #6015 comment 5449426611.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants