Skip to content

fix(security): withdraw the ADR-0091 D5 recertification claim from the attestation columns (#9046) - #9669

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-9046-d5-recertification-claim
Aug 18, 2026
Merged

fix(security): withdraw the ADR-0091 D5 recertification claim from the attestation columns (#9046)#9669
os-project-manager merged 2 commits into
mainfrom
claude/issue-9046-d5-recertification-claim

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#9046

The ADR-0091 D5 attestation pair — last_certified_at and certified_by, declared on both grant tables — stops asserting a recertification review the platform does not run. Prose only: no producer and no consumer is added, and no column is dropped.

Premise re-verified on current main before any edit

The card is a negative claim from 2026-08-16, so its sweep was re-run at branch point 07e630e58, over packages/, apps/ and examples/, every .ts/.tsx, tests included:

git grep -n -E 'last_certified_at|certified_by' -- 'packages/**/*.ts' ... 'examples/**/*.tsx'

20 hits, in exactly two kinds of place: the two .object.ts declarations, and the four generated i18n bundles that carry their strings. No producer, no consumer — nothing stamps either column, nothing reads either one, and nothing derives "never certified" or "certification stale". Whole-tree with no file-type filter adds only CHANGELOG prose, docs/adr/0091-*.md, and one docs page that already describes them accurately as lifecycle-audit columns whose campaign is enterprise product.

The card's corroboration keys were used as the control, so a blind search could not read as a clean one — all three resolve:

control keyresolves at
granted_bypackages/plugins/plugin-sharing/src/sharing-plugin.ts:873
valid_untilpackages/plugins/plugin-security/src/delegated-admin-gate.ts:477
reasonpackages/lint/src/validate-security-posture.ts:626-631

valid_from/valid_until are additionally enforced through isGrantActive at resolution time. Only the D5 pair comes back empty on both sides. Premise stands.

Two near-misses were checked and are not consumers: packages/spec/src/security/explain.test.ts:8 mentions "recertification review UX" in a comment about enterprise consumers of explain, and the certification* keys in packages/spec/authorable-surface* belong to the unrelated training schema.

What changed

Both descriptions now state the inertness outright rather than merely omitting the promise, keeping the ADR-0091 D5 citation. Old text on both objects:

[ADR-0091 D5] When this grant was last attested in a recertification review. Null = never certified.
[ADR-0091 D5] Reviewer who last attested this grant.

New text says the columns are reserved for a future access-recertification workflow, that no platform code writes or reads them, that no resolution path, gate or lint consults them, and that null means the workflow does not exist rather than that the grant went unreviewed. Each declaration carries a [#9046] comment with the measurement and the disposition, in the shape sys_capability.active uses.

Why prose and not the other two dispositions — ADR-0049 enforce-or-remove, settled the way sys_capability.active was (maintainer ruling, 2026-08-13): enforcement means building a SOX/ISO access-review feature with zero measured pull, and removal costs a migration over existing rows while buying nothing the prose fix does not. The harm is the promise, not the storage, and reversibility runs the right way — a description is one line to change back if D5 is ever implemented.

Scope of the negative claim is this repo. An objectui designer preview rendering these columns would count as a consumer under the 2026-08-10 ruling; that is neither enforcement nor a producer, and the new descriptions are worded about platform behavior, not about every renderer.

The i18n locales — one deviation, stated

node scripts/check-i18n-bundles.mjs --write --filter=security moved enonly. That is inherent, not a slip: extraction merges against the committed bundles (mergeExisting: !flags['no-merge'] in packages/cli/src/commands/i18n/extract.ts) and only the default locale is filled from schema labels, so a locale already holding a hand translation is deliberately never overwritten. --fill=default fills gaps, and these were not gaps. The two generator-only alternatives were both worse: --no-merge wipes every hand-translated string in the bundle (the incident the extract config's own comment warns about), and deleting the leaves to force a --fill=default refill would replace three localized help strings with English.

So the three localized help leaves were updated in place — the maintenance path the generated header names: "Edit translations in place; re-run extract (with --merge) to fill new gaps. Do not hand-edit the structure — only the leaf string values." Keys and structure untouched; pnpm check:i18n re-extracts and reports plugin-security's 4 bundles in sync, which is the proof the edit is reproducible rather than drift. All four locales moved, two fields on each of the two objects.

Changeset

Owed and added (patch, @objectstack/plugin-security). The descriptions are user-visible — they ship as field help text and as the four locale bundles — and without a version bump the correction never reaches npm.

Verification — all at the final commit d0f90f63d

Gate union re-derived from the actual changed paths with node scripts/pm/dispatch-gates.mjs off git merge-base (the changeset file pulled in five gates the pre-change derivation did not name):

gateresult
pnpm check:i18nOK, 9 packages, all bundles in sync (ran the built CLI — not a refusal)
pnpm check:changeset-gate-self-testsOK
pnpm check:objectui-changesetOK
pnpm check:cross-package-test-inputsOK
pnpm check:test-source-aliasOK
pnpm check:type-source-resolutionOK
pnpm check:nul-bytesOK, 6181 files, no control bytes
check-adr-0087-registration.mjsOK, no declared-breaking changeset
check-changeset-no-major.mjsOK
check-empty-changeset.mjsOK, 1 declaring changeset added
check-affected-docs.mjsOK
pnpm --filter @objectstack/plugin-security test66 files, 1292 tests passed
pnpm --filter @objectstack/plugin-security typecheckOK

Full workspace build green first (turbo run build, 71 tasks) — check:i18n refuses an unbuilt tree, and plugin-security was rebuilt again after the bundle commit so its dist matches the edited src.

No test is added: there is nothing behavioral to pin, and a test asserting these columns do something is exactly what the card forbids.


Generated by Claude Code

…from the attestation columns
`last_certified_at` / `certified_by` on `sys_user_permission_set` and
`sys_user_position` are the ADR-0091 D5 substrate and nothing more: a
whole-tree sweep finds them only in these two declarations and the generated
i18n bundles. Nothing stamps them, nothing reads them, no surface derives
"never certified" or "certification stale".
Their descriptions stated D5's intent as though it were the behavior. On a
compliance surface (SOX / ISO 27001 access review) that reads as evidence of
a control the platform does not have. ADR-0049 enforce-or-remove, settled the
way `sys_capability.active` was (maintainer ruling, 2026-08-13): the claim is
withdrawn in prose rather than the workflow built or the columns dropped.
Co-Authored-By: Claude <noreply@anthropic.com>
…ion into zh-CN/ja-JP/es-ES
`node scripts/check-i18n-bundles.mjs --write --filter=security` regenerates the
default locale from the schema labels, which moves `en` only: extraction merges
against the committed bundles (`mergeExisting: !--no-merge`), so a locale that
already holds a hand translation is deliberately never overwritten — `--no-merge`
would wipe every hand-translated string in the bundle. The three localized
`help` leaves are therefore updated in place, which is the maintenance path the
bundle header names ("Edit translations in place ... only the leaf string
values"); keys and structure are untouched, so a re-extract reproduces them.
Adds the changeset: the descriptions are user-visible, and without a version
bump the correction never reaches npm.
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security, touching 4 documentable anchor(s).

8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/approvals.mdx(via sys_user_position (symbol))
  • content/docs/data-modeling/objects.mdx(via SysUserPermissionSet (symbol), sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/permissions/administrator-guide.mdx(via sys_user_position (symbol))
  • content/docs/permissions/authentication.mdx(via sys_user_position (symbol))
  • content/docs/permissions/authorization.mdx(via sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/permissions/delegated-administration.mdx(via sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/permissions/permission-sets.mdx(via sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/permissions/positions.mdx(via sys_user_position (symbol))

6 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx(via sys_user_permission_set (symbol))
  • content/docs/releases/v13.mdx(via SysUserPosition (symbol), sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/releases/v14.mdx(via sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/releases/v15.mdx(via sys_user_position (symbol))
  • content/docs/releases/v16.mdx(via sys_user_permission_set (symbol), sys_user_position (symbol))
  • content/docs/releases/v17.mdx(via sys_user_position (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see

Coarse fallback — 13 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d693ba158cdc392e244374a8b2d1cb96036d9b02packageMentionDocs.

Which tree this was computed on

This run read content/docs from c18fbc52ce4ca449a0b46323c9617d04befe2400 — the merge of head d0f90f63d288111871c009d3f659224de4ccf034 into base d693ba158cdc392e244374a8b2d1cb96036d9b02, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c18fbc52ce4ca449a0b46323c9617d04befe2400 && git checkout c18fbc52ce4ca449a0b46323c9617d04befe2400
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d693ba158cdc392e244374a8b2d1cb96036d9b02 d0f90f63d288111871c009d3f659224de4ccf034 && git checkout -B drift-repro d693ba158cdc392e244374a8b2d1cb96036d9b02 && git merge --no-ff d0f90f63d288111871c009d3f659224de4ccf034
node scripts/docs-audit/affected-docs.mjs --json d693ba158cdc392e244374a8b2d1cb96036d9b02

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d693ba158cdc392e244374a8b2d1cb96036d9b02 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-project-managerClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM ruling on the one open question — the i18n deviation. Option A stands; the fence was wrong, not the implementation.

My dispatch fence said regenerate, do not hand-edit. That fence assumed a regeneration path exists that reaches the three localized bundles. It does not, and the PR measured why rather than asserting it: extraction merges against the committed bundles (mergeExisting: !flags['no-merge'], packages/cli/src/commands/i18n/extract.ts) and only the default locale is filled from schema labels, so a locale already holding a hand translation is deliberately never overwritten. --fill=default fills gaps, and a stale-but-present string is not a gap.

So the fence, read literally, has no satisfying move — every generator-only option is worse:

  • B (delete the leaves, refill) replaces three localized strings with English: it degrades the locales in order to fix the promise, and the promise is the thing the card is about.
  • C (--no-merge) wipes every hand-translated string in the bundle — the exact incident the extract config's own comment warns about.
  • D (en only) leaves the false compliance claim standing verbatim in three of four locales, which is most of the harm.

A is the maintenance path the generated header itself names"Edit translations in place; re-run extract (with --merge) to fill new gaps. Do not hand-edit the structure — only the leaf string values." Keys and structure untouched, and pnpm check:i18n reporting plugin-security's four bundles in sync is the proof it is reproducible rather than drift, which is the property the fence actually wanted. Nothing to revert.

The gap behind the question is real and I am recording it, not leaving it with you — this repo has no mechanism that propagates a changed source description into an already-translated locale. Every such correction lands as a hand edit that no gate can distinguish from ordinary translation drift; check:i18n proves keys are in sync, never that a translated value still means what its source says. That is the same shape as the class this card belongs to — a declared surface (the locale bundle) that asserts something no enforcement keeps true. Filing it unassigned as a finding rather than asking you to.

Flipped to ready and auto-merge armed. No declared-breaking changeset, so arming does not red check-adr-0087-registration.


Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationsize/mtooling

Projects

None yet

2 participants

@os-project-manager@claude