Repository navigation
feat(plugin-security): a boot report lists, per organization, every catalog reference whose name resolves nowhere (ADR-0131 D3/D4, C3 stage 3) - #22671
Conversation
…apped; grant-name backfill warning says confers nothing Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
…port Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
…t pin Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rerax7QTjKMPCUZxQUtPFR Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 40 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 16 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 7c291ac65d11a4b3a25ca2acf3f501c9994d2acc && git checkout 7c291ac65d11a4b3a25ca2acf3f501c9994d2acc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bfc15d275ba3fed8406cd5836b826e617150fae0 cafbe826a8f6333b1f9db9f8ddda44a3a107528c && git checkout -B drift-repro bfc15d275ba3fed8406cd5836b826e617150fae0 && git merge --no-ff cafbe826a8f6333b1f9db9f8ddda44a3a107528c
node scripts/docs-audit/affected-docs.mjs --json bfc15d275ba3fed8406cd5836b826e617150fae0
|
Brings in stage 1 (bfc15d2): the resolver reads positions' permissionSets from the security catalog and deactivation from sys_metadata_activation. Claude-Session: https://claude.ai/code/session_01VUp6VuRm8SBjud6b5CwjSV Co-authored-by: Claude <noreply@anthropic.com>
…talog reference report Stage 1 moved the authorization resolver onto the security catalog, so the case that was parked behind it runs now. Its setup comment no longer says the junction row is a binding the resolver reads. Claude-Session: https://claude.ai/code/session_01VUp6VuRm8SBjud6b5CwjSV Co-authored-by: Claude <noreply@anthropic.com>
|
os-dev-report addendum (stage 3, post-stage-1 patch round) · cloud dev Dispatched by epic PM Head:
Size against the new merge base The enabled case. In Premises re-verified on the merged head
Measured at
Open (to the PM, not built here): a junction-only binding is invisible to this report. A position whose definition resolves, but whose sets are bound only through Generated by Claude Code |
Part of #15204
Clause-②: no
Stage 3 of the C3 cutover stage plan (
6094501866on #15204): the S9 dangling-name boot report, under the claim amendment6095710107. #15204 stays open for its other stages; this PR carries no closing keyword. The S9 content is the list #15196's releases carried to it (6080018188,6084206172,6093100744), with the rulings Q3 A (6050490870) and Q (a) (6094045326).Landing order: after stage 1. The report's "confers nothing" holds for a named reference only once the authorization resolver reads positions, sets and set bodies from the catalog (stage 1). One assertion is pending stage 1 (below).
What changes
catalog-reference-report.ts(new,@objectstack/plugin-security). It reads three tables:sys_user_position,sys_user_permission_setandsys_position. Every name is resolved throughcreateSecurityCatalogReader(@objectstack/core, already onmain).collectCatalogReferencesreturns a structured result;reportCatalogReferencesalso logs it, one line per kind of finding. The report writes nothing.security-plugin.ts: onekernel:bootstrappedhook, registered after the grant-name backfill and the row-only position backfill. Handlers run in registration order, so a name either backfill resolves during the boot is not reported. Nothing runs without a hook-capable context: a report atstart()would judge a catalog that is still filling.grant-permission-set-name-backfill.ts: two warning sentences and two module-doc lines that said such grants "still resolve by id today". That stopped being true when the resolver began reading grants by name (C2 stage S5a). They now say these grants confer nothing until a later boot names them.packages/qa/dogfood/test/position-held-name-setup-create.dogfood.test.ts) for S10's Setup answer (E2)..changeset/15204-s3-boot-report.md(@objectstack/plugin-security, minor). It names what the report lists and where the operator reads it.Which S9 items this covers
sys_user_position.position,sys_user_permission_set.permission_set)positionAssignments,permissionSetGrantsper organizationcatalog-reference-report.test.ts: "an assignment and a grant whose names resolve nowhere are listed under their own organization, and nowhere else" (three buckets: organization-less, two organizations)unnamedGrantsper organization, with the grant'spermission_set_id; every unnamed grant is listed, whatever the reason the backfill left it unnamedpositionsWithoutDefinitionper organization under a walled posture. The organization's seeded built-in copies resolve and are not listed.saveMetaItem, typeposition) clears the position from the next reportsinglebefore S10 (Q (a))conflictingPositionNames, undersingleonly: every name more than onesys_positionrow carries, with each row's organization and whether the catalog resolves the name. Reported as conflicting, never merged.positionsWithoutDefinitioncarriesrefusedName. The S7 backfill records its verdict and stops reporting the class; this report lists it at every boot.single:POST /api/v1/data/sys_positionundermanager(package-held) andeveryone(built-in anchor) answers403 NOT_OVERRIDABLE. The rows stay as they were and no environment definition is written. A rename intomanageranswers the same.grant-permission-set-name-backfill.tsAlso pinned:
warn.insert/update/deletecalls.The fail-closed half: pending stage 1
The stage-plan test reads "a removed declaration fails closed and is reported; re-adding it clears both". On this tree the report half holds and is active ("the grant and the assignment are listed while the declarations are gone, and drop out once they are back"). The resolver half does not hold yet, so it is written but skipped:
packages/plugins/plugin-security/src/catalog-reference-report.test.ts,it.skip('PENDING STAGE 1: the removed declaration fails closed at the resolver, and re-adding it restores the grant'→it(.2d5ca52d, un-skipped throughscripts/ablation-replace.mjs, restore proven blob == HEAD): it fails at thefails closedassertion. The holder still gets{ auditor: true, bit: true }after the declarations are removed, because the set row and the junction row still grant.claude/issue-15204-s1-position-permission-setsat8cbedf96, with this branch's commits cherry-picked into a throwaway worktree and the test un-skipped):Tests 13 passed (13). The pending case passes there, and the other twelve pass beside stage 1's resolver. The worktree was then removed.Measured on the real composition
single). The boot log carries, in order: the grant-name backfill's record line, the position backfill's record line, then the report's line, "every position assignment and permission-set grant names an item the security catalog resolves at this boot, and no position row is without a definition". So there is no false positive on a fresh showcase.heldPositionNameRefusalwas made to returnnull(scripts/ablation-replace.mjs, thenpnpm --filter @objectstack/plugin-security build).scripts/ablation-dist-preflight.mjsfound the marker indist/index.jsanddist/index.mjs. The pin then went 3 red / 1 green: the creates ofmanagerandeveryoneanswered201and landed an organization row beside the seeded one, and the rename answered409. The precondition case stayed green. Restored, rebuilt, and--absentconfirmed: the marker is absent from all 6 built files and the tree is clean.Tests and gates
pnpm --filter @objectstack/plugin-security test(full package)613a5b56(pre-merge)1 failed, 4112 passed, 46 skipped. The one failure was the handler-count pin below. After the fix (54864c2a), the three touched files re-ran71 passed, 1 skipped.pnpm --filter @objectstack/plugin-security typecheck(incl. the test layer)115660d7catalog-reference-report.test.ts115660d712 passed, 1 skippedposition-held-name-setup-create.dogfood.test.tsaafa8cd94 passednode scripts/pm/dispatch-gates.mjs --commands(70 families) +check:startup-registry-verdict+check:durability-log-levelad45b764(origin/main5fb17466merged)--ranreconciliation: 70 derived, 70 run, 0 NOT-MEASURED (derived from recorded exit codes)ad45b764(origin/main5fb17466merged)grant-permission-set-name-backfill.test.tspinned thekernel:bootstrappedhandler count at 2. It is now 3, and its comment names the report.check:query-options-erasurecounted a new test-surface erasure (236 → 237): a pass-throughfindoptions argument cast toany. It is now typedEngineQueryOptions, and the ratchet holds at 236.@objectstack/plugin-security. Its published surface is byte-unchanged (nothing new is exported), but every boot that composesSecurityPluginnow runs the report.Acceptance notes
positionrecipient expands its holders fromsys_user_positionrows (plugin-sharing/src/sharing-rule-service.ts,recipient_type === 'position'), independently of the catalog. So a removed position declaration keeps its sharing-rule grants until that reader moves. That move is planned in stage 2 (the services readers). The report lists the assignments either way.saveMetaItem,actor: 'system') underisolated. Whether a platform administrator'sPUT /api/v1/meta/position/NAMEis accepted under a wall was not measured here.sys_position. Stage 8 retires that object. After that they read nothing and name the object as absent, so their population is C7's migration report from then on.NOT INquery on the resolved names: under a case-insensitive collation (MySQL's default) such a query would hide a case-variant dangling name.metadataDoorAcceptsPositionNameis imported fromposition-write-through.ts, which stage 7 retires. The S7 backfill imports it too. It is S7's predicate, read and never re-derived.permission-set-projection.tsandper-organization-catalog.ts. The report reads organization ids with a local three-line helper rather than adding a consumer toper-organization-catalog.ts.Generated by Claude Code