Uh oh!
There was an error while loading. Please reload this page.
fix(security): retire delegated_from from sys_user_permission_set (ADR-0049 enforce-or-remove, #9730) - #9998
Conversation
…R-0049 enforce-or-remove) The runtime delegation gate is structurally scoped to sys_user_position, so on the permission-set grant table the column was declared and data-door- writable while no runtime consumer read it. Maintainer ruling 2026-08-18: REMOVE. Lint D3 rule scoped to the position table; docs per-object grant- column table updated (#9876); ADR-0087 semantic ledger entry ups-delegated-from-column-retired registered. Fixes#9730 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
📓 Docs Drift CheckThis PR changes 3 package(s): 25 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 118 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 078e1bb8c9d407d4c40b6b75a796e8595aa8cc74 && git checkout 078e1bb8c9d407d4c40b6b75a796e8595aa8cc74
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fe9447b0c0c32c0f2e936b02be0b36c341e081a1 00a79d51eb0c61e39c76678374ad2237903fa169 && git checkout -B drift-repro fe9447b0c0c32c0f2e936b02be0b36c341e081a1 && git merge --no-ff 00a79d51eb0c61e39c76678374ad2237903fa169
node scripts/docs-audit/affected-docs.mjs --json fe9447b0c0c32c0f2e936b02be0b36c341e081a1
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9730
Part of #9876 — this PR carries the
authorization.mdxhunk that card mandates ride the removal PR; per that card's own text it should be closed as absorbed when this merges (left to the unlock scan / PM rather than a closing keyword, since its wording is "close as absorbed if the hunk rode along").What
Retires
delegated_fromfromsys_user_permission_setunder ADR-0049 enforce-or-remove — maintainer ruling 2026-08-18 on #9730: REMOVE. The runtime delegation gate is structurally scoped tosys_user_position(isDelegationWriteatdelegated-admin-gate.tsreturnsfalsefor every other object, makingassertSelfDelegationunreachable here), and the explain engine reads delegation provenance from position rows only. On this table the column was declared and data-door-writable while no runtime consumer read it — authoring-lint-only enforcement on a security object.Three declared surfaces, plus the ledger the ruling's "ADR-0087-style registry entries as applicable" clause forces:
plugin-security— the field declaration is removed (a retirement note replaces it, naming the ledger entry); the stale half of the#9046comment block ("reason/delegated_from are read by the delegated-admin gate and the security-posture lint") is corrected; i18n bundles regenerated (pure deletion of the retired entry, all four locales — thesys_user_positionentry is untouched). Negative pin added inrbac-objects.test.ts: the object no longer declares the column; sibling positive pin:sys_user_position.delegated_fromstays declared as asys_userlookup.lint—validate-security-posture.ts: the two ADR-0091 seed rules deliberately stop sharing one object scope. Measured, not dropped wholesale: D2 (valid_untilexpired/unparseable at authoring) still covers both grant tables —valid_untilremains declared and resolution-enforced onsys_user_permission_set(the existing unparseable-valid_untiltest on that very object pins this). D3 (delegation row must carryreason) is scoped tosys_user_positiononly, the one table that declaresdelegated_from. Fixtures triaged per the ruling: the reachability-corpus fixture and the runtime-gate fixtures that used a permission-set delegation row now use position rows (they pinned the branch the ruling removed); a new negative pin holds that a permission-set seed carrying the retired key draws no D3 finding at the real runtime gate.content/docs/permissions/authorization.mdx(Thedelegated_fromremoval onsys_user_permission_setmust updateauthorization.mdx's per-object grant-column table in the same PR #9876) — the four-column D1 intro now statesdelegated_fromexists only onsys_user_position; the per-object table'ssys_user_permission_setcell fordelegated_fromreads "Not declared — retired" with the refusal channel; the seed-lint paragraph no longer claims the D3 rule "runs on both grant tables". The D5 pair rows and thereasonrow are unchanged, as Thedelegated_fromremoval onsys_user_permission_setmust updateauthorization.mdx's per-object grant-column table in the same PR #9876 specifies.ADR-0087 disposition (measured against what
check-adr-0087-registrationdemands): the changeset is declared-breaking and carries a FROM→TO prescription, which closes theno-migration-prescriptionescape — so the honest category isregistered. This is a platform-object column, not an authorable spec key, so the bookkeeping follows theaudit-log-action-enum-retiredshape: a semantic entry (ups-delegated-from-column-retired, new file underentries/semantic/,gen:migration-registry+gen:spec-changes+gen:upgrade-guideregenerated), nothing inRETIRED_KEYS_BY_MAJOR, and the four surface ratchets byte-identical (verified: noauthorable-surface/,api-surface/, orjson-schema.manifest/file moves in this diff).Migration posture — measured, not assumed
git grep -n "delegated_from" -- packages apps examples ':!*CHANGELOG*'atb3042e328): zero production writers on this object; the only literals naming both the table and the column are lint test fixtures. The platform's own writer to this table (auto-org-admin-grant) stampsreason/granted_by, neverdelegated_from.DROP COLUMNis issued; the platform stops declaring, projecting and accepting the column. No data migration is written, and none is required.400 INVALID_FIELDbefore the driver or any hook runs (Undeclared fields still reach the driver: beforeInsert hooks run and an auto-number is consumed before the request is refused, and the whole INSERT with its values is logged at ERROR (17.0.0 GA) #8682 preflight), so a stale seed or client write is told exactly what to remove — strictly louder than the advisory lint that used to be this column's only coverage.Ruling guardrails held
origin/main) never touchessys-user-position.object.tsor any position-table path; targeted green run ofdelegated-admin-gate.test.ts+explain-engine.test.ts+rbac-objects.test.ts(139 tests) on the final tree.Ablation (standing lane clause)
Re-added the field declaration → predicted the negative pin's signature (
rbac-objects.test.ts› "no longer declaresdelegated_from" fails onexpect(fields).not.toHaveProperty('delegated_from')) → observed exactly that red → restored byte-identically, hash-verified (git hash-objectidentical before/after:5982129da6c08723838bdda3d3c73387a74b03fc). Both legs ran against source via same-package relative imports (nodistresolution on this pair), so no rebuild step applies; the restore leg re-ran the pin green.Verification (all at head
00a79d51e, clean tree)pnpm --filter @objectstack/plugin-security test— 66 files, 1295 tests passed; package typecheck exit 0.pnpm --filter @objectstack/lint test— 76 files, 2107 tests passed; package typecheck exit 0.@objectstack/spec:vitest run src/migrations src/conversions— 5 files, 307 tests passed (chain replay + fixture disjointness over the regenerated registry); package typecheck exit 0.dispatch-gates.mjsderived from the actual merge-base diff, 31 gates): all PASS at00a79d51e— includingcheck:adr-0087-registration,check:changeset-no-major,check:i18n,check:migration-registry/check:spec-changes/check:upgrade-guide, the docs family, the spec-liveness family,check:cross-package-test-inputs,check:engine-double-contract,check:where-matcher,check:type-check-coverage, andcheck:type-check-debt(after building the workspace closure, as lint.yml does).check:dev-prereqsinitially FAILED on its own stated precondition ("the workspace is not built" — 12 packages this task never touched had no localdist); green afterpnpm build. Per-gate exit codes were captured before any pipe.Out-of-scope finding filed: #9997 (ADR-0091 D1's declaration list still names the column on both tables —
docs/adr/is governed and outside this PR's ruled surface).Generated by Claude Code
Generated by Claude Code