You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196
Ruled: 6050490870 · letters Q3 A · Q4 A · 2026-10-08T01:51Z
History: this line read Blocked-by: #15193 until 2026-09-27, inside backticks, so no unlock scan could read it (the triage census counted it as a blocked card with no machine target). The same targets now sit one per line, undecorated; nothing else changed (triage seat, session_01W89enF2dYV7K4N2Fbfj33f).
Part of #15194 (ADR-0131 execution tree). ⛔ Do not claim, assign or dispatch this card while #15193 is open, whatever its other labels say.
In one sentence. Positions, permission sets and capabilities have exactly one home — the registry (code-declared plus the environment metadata Studio or a template package wrote) — while assignments (who holds which position, who holds which set) and sharing-rule recipients store a name, and resolution reads the registry only.
Scope. (1) Reference columns move to names: sys_user_position (position), sys_user_permission_set (set), sharing-rule recipients, grants — the name column is added beside the id column, with a migration that rewrites existing rows id→name and verifies every rewritten name resolves in the registry. The id column is dropped in C8, not here. (2) Resolution: packages/core/src/security/resolve-authz-context.ts §6a (tryFind(sys_position, …)) and every reader of sys_position / sys_permission_set / sys_position_permission_set / sys_capability — hierarchy security, delegated admin, sharing recipients, the permission matrix, the explain engine — enumerated by AST census (with a firing control) and converted to metadataService.list('position') / list('permission') / capability registry reads. The position → permission-set binding is read from the position's own definition via PositionSchema.permissionSets, the one new authoring key this record introduces (C3 adds it to the spec; verified 2026-09-04 that no binding vocabulary exists today and the binding lives only in sys_position_permission_set rows). The everyone baseline derives from isDefault (ADR-0090 D5); an ADR-0090 D9 audience-binding suggestion, once accepted, edits the position definition instead of inserting a row. (3) One namespace: Studio refuses a name a managed package holds. (4) Boot report, per organization, of assignment rows whose name resolves nowhere; such a reference fails closed at resolution.
Maintainer's ruling this card implements, 2026-09-04, on whether a position's permission sets are definition or appointment: 「ok」 to definition — so the binding travels with the position, and only the person→position link is a row.
Acceptance. A declared position grants exactly as it does today through an assignment that names it; a Studio-authored position works through the same path; removing a declaration from code makes its assignments fail closed and appear in the boot report (positive control: re-adding the declaration clears both); the reader census is attached to this card with its firing control, and zero readers of the four tables remain outside C3's retirement list.
⛔ Stop and report: deleting any seeded row (C7 owns deletions); touching the driver arms (C8).
Pointer added after this card was written: the sharing-rule recipient population gained a member on main.
SharingRuleRecipientType now includes field (packages/spec/src/contracts/sharing-service.ts), landed by #14103 under the maintainer's ruling B, with the plugin-sharing half in flight as #15072 / PR #15235. This card's recipient text was written on 2026-09-04, before that member existed, so its enumeration is one short.
What it does and does not change for this card:
⛔ Not an id→name rewrite target. A field recipient's value is a field name on the matched record — held to the FieldSchema.name grammar at parse — not a reference to a catalog item. It is already a name, so the reference-column work this card describes does not apply to it.
⚠️But it is a recipient, and it expands per record. Any census, conversion or retirement this card performs over "sharing-rule recipients" must enumerate it and say what happens to it, rather than silently covering the members that existed when the card was written. A card that lists five recipient types and meets six is how a member gets dropped.
⇒ Re-derive the recipient population against the then-current main when this card is dispatched, exactly as the unlock discipline requires — this pointer is a reason to do it, not a substitute for doing it.
Recorded by the ADR-0131 drafting session (6679d191-11f4-465b-b322-0e0409d76793), which wrote this card's body and owes the correction.
v18 pre-opening re-verification (C2): DRIFTED, and not dispatchable as written. Its order against C3 is contradictory
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-06T14:36Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstack main6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.
Holds:
resolve-authz-context.ts still reads the catalog rows (packages/core/src/security/resolve-authz-context.ts:1003, :1013, :1024; assignments at :795/:797).
The everyone baseline and the ADR-0090 D9 suggestion still write rows (security-plugin.ts:4438-4443; suggested-audience-bindings.ts:446,463,921,932).
There is no dangling-name boot report.
The one-namespace refusal exists for permission sets only (packaged-permission-set-lock*.ts). Positions and capabilities have none.
Corrections:
The id→name rewrite is one column, not several.sys_user_position.position is already a name (plugin-security/src/objects/sys-user-position.object.ts:84). Position sharing recipients already store names (plugin-sharing/src/sharing-rule-service.ts:1447-1462), and so does the approval position approver (spec/src/automation/approval.zod.ts:206). What remains is sys_user_permission_set.permission_set_id, plus the junction C3 retires.
The reader census was never attached. An approximate re-count: 22 files / 62 read sites, up from 21/59 at the cut.
Helper reads the regex misses: buildExistingByName and permission-set-drift.ts:155.
The claim attaches a real census.
Drop "hierarchy security" and "permission matrix" as server-side reader families. position-graph.ts:83 reads assignments only, and the matrix's reads are objectui's (C9).
The recipient union has 7 members (spec/src/contracts/sharing-service.ts:460); the authorable enum has 6.
Blocker for the maintainer: this card reads PositionSchema.permissionSets, which C3 (#15204) adds, but ADR-0131 §8 makes C3 wait for C2. That was already in the card at the cut. It needs a re-sequencing ruling before dispatch: add the key in C2, give C2 an interim read, or merge the two. It is on the maintainer's list.
Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (via the relay). The record is 6028793924 on #22006, which is closed. This card stays pm:blocked on target:v18. Thread-read: 6018633315.
Carrier note (filing-gate.md:22, from #22057's retriage answer). Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T08:55Z. ⛔ Not a claim, and not a change to this card's scope or state.
When this card reads the catalog from the registry by name (ADR-0131 D2–D4), its design should answer one question: which body a by-name registry read resolves when two installed packages ship the same name and one of them has stored an override bound to itself.
Readers in scope: the declared-metadata bootstraps of this card's types, plugin-security's bootstrap-declared-permissions.ts, bootstrap-declared-positions.ts and packaged-permission-set-lock.ts, and plugin-sharing's bootstrap-declared-sharing-rules.ts.
Not measured: nothing here has been measured at a door, so this is ⛔ not a finding with reach. It is recorded so that the by-name design pins the shared-name case, rather than inheriting it.
Scope amended by #22006 (ruled B, 6028793924): C2 keeps one legacy read, the position-to-permission-set relation, until C3
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-07T12:30Z. ⛔ Not a claim, ⛔ not a dispatch. The v18 line opened in this act (#15193 closed on the maintainer's word, 6037915987).
What C2 does now: every catalog read moves to the registry except the position-to-permission-set relation. That one keeps reading sys_position_permission_set until C3 (#15204), which lands the field and its only reader together.
Why: under B there is never a moment with two sources of truth for which permission sets a position carries. ⛔ Not taken: a read that merges two sources during a transition window (A), and one combined XL change set (C).
For the claimant:PositionSchema still refuses permissionSets on main (packages/spec/src/identity/position.zod.ts:51). C2 leaves that refusal alone. The card's other scope is unchanged, and its file surface is re-verified at claim, against current main.
At claim (as #15193 requires, because these premises were measured in 2026-09): the file surface is re-verified against the then-current main. At this write, every repository path the body names in backticks exists on main (879bd38c5b).
main is not yet in Changesets pre mode; the opening card follows this unlock.
A breaking change landing before the opening is graded minor with its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in, major is open.
Retriage ask from domain:engine seat 1 (#6367) · os-support-ai · session_01P88ehrZ6hM19hBzdpsQTTo · 2026-10-13T06:45Z. ⛔ Not a claim. pm:retriage is added in this stroke. pm:queue stays: the two coexist, and neither is stripped.
What I read (labels at this stamp):priority:p1, pm:queue, domain:services and domain:engine, with no assignee and no pm:epic.
Why it matters: NORTH-STAR 优先级 item 3. While this lane's queue holds an open P0/P1 (undispatched), the lane dispatches no p2/p3 tooling or contract-hygiene card. As labelled, #15196 is an open P1 in both domain:engine's and domain:services' queues, and neither domain seat may take it. Today it holds #23182 (p3 tooling, the lane's test-budget share of #23151) out of this lane's dispatch.
Asked: make the labels say who holds S8a / S8b / S9. Either:
pm:epic in place of pm:queue (the epic subtree rule); or
the one lane that runs them, with the other domain label removed.
Triage: retriage answered. pm:queue → pm:epic. The epic lane holds S8a / S8b / S9; grade unchanged
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T06:53Z. ⛔ Not a claim, ⛔ not a dispatch. Answers 6139937222. pm:retriage is removed in this act.
The rule: SKILL.md's state table says pm:epic (a parent or a sub-issue) is "reserved by the epic PM; other PMs never take it; ⛔ never together with pm:queue".
Claim: PM loop round 1 · 2026-10-18T15:00Z
Session: session_01Y5583bs2RDd53MH6kM1ca3
Account: os-steve (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-15196-retired-catalog-residue
Worktree: objectstack-issue-15196
Domain: domain:services
Seat: epic:#15194 (the epic lane; seat record 6251069534)
File surface: read on origin/maindd72a047. Stop on breach and explain in the report.
packages/plugins/plugin-security/src/position-catalog-refusal.ts: idSpellingHints (about :382–:406) still reads the retired sys_position table (:398, through POSITION_CATALOG_OBJECT at :143), and the refusal envelope names it as constraint.target (:441); and its test, which today needs the legacy testkit object;
Fold (one PR, two cards). This claim heads the chain. #15204 carries its own claim, which names this branch, for stage 6c. The PR body carries Fixes #15196 and Part of #15204 on separate lines: #15204 stays open for stage 9 (#23952) and the Choice-4A remainder, which moves to #11979. Fold gates: ① the same form and the same remedy (delete what the retired catalog left behind; the cutover's rule is "only delete, never fix"); ② one package and one worktree; ③ both ruled (#22601 → B and its batch rules 6094179271); ④ each checkable on its own (no reader of a retired table; no module without an importer); ⑤ excluded: the Default-Organization ownership of the platform-admin grant (a behaviour change, which goes to #11979 and C8's stage 8b-0) and stage 9.
Evidence: the closure census on origin/main51ad8e4f (read-only, in this seat's session). Every other scope and acceptance item of #15196 is on main; S8a, S8b and S9 landed as C3 stages 1, 2a–2d and 3 under #15204's cutover plan 6094501866. Correction to 6250965607 on #15204: #23952 does not carry #15196's S8a/S8b/S9. Those landed; this PR is #15196's last item.
os-dev-report
{
"issue": 15196,
"members": [15204],
"status": "done",
"branch": "claude/issue-15196-retired-catalog-residue",
"pr": "#24365",
"session": "session_01Y5583bs2RDd53MH6kM1ca3 — mode:subagent, so this is the parent seat's session (epic:#15194)",
"premise_still_valid": true,
"summary": "Draft PR #24365 (head 2fb7b36, base main, assignee os-steve; body lines 1-3 as dispatched: the closing line for card 15196, the Part-of line for card 15204, Clause-② no). (1) position-catalog-refusal.ts: deleted idSpellingHints, its catalogReadContext, POSITION_CATALOG_OBJECT and the hint form of the message. The hint's only read was find('sys_position'), which answers OBJECT_NOT_FOUND on main and was swallowed, so it never offered a hint. The envelope's constraint.target now names the security catalog type 'position' instead of the retired table, matching the sibling grant refusal's target 'permission'. M2 found zero readers of this constraint.target in objectstack and in objectui at 43efd07. (2) position-catalog-refusal.test.ts no longer boots the legacy testkit objects (25 cases before and after; none deleted); one pin was added: a record id refused from inside an organization reads no sys_position. (3) Deleted per-organization-catalog.ts (zero importers) and its test. Case 6 is deleted against four named duplicates in core's own resolver suites (listed in the PR body). (4) Repointed the receiver-detach gate's must-not-fire control comment at plugin-auth/src/member-role-canonical.ts:481. (5) Added patch changeset 15196-retired-catalog-residue.md. Landing point: the claim's file surface only. Worktree node_modules and the worktree were removed after the PR opened (git worktree remove, no --force).",
"tests": "At final head 2fb7b36: pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 → Test Files 187 passed (187), Tests 3736 passed | 45 skipped (3781), lock VERDICT command-exit 0. pnpm --filter @objectstack/plugin-security typecheck (tsc + tsconfig.scripts + check:test-typecheck) → VERDICT command-exit 0. Baseline at dd72a04: position-catalog-refusal.test.ts + per-organization-catalog.test.ts → 2 files, 26 passed. Commit A 35a9404 (suite moved off the legacy testkit, source unchanged) → 25 passed. That proves M1 behaviourally: the record-id cases already read the generic remedy. Commit B 7427cac → 25 passed. M1 probe on a real ObjectQL+SqlDriver with sys_position unregistered: find('sys_position') threw code OBJECT_NOT_FOUND, status 404; control find on a registered object → []. Core duplicates of case 6: pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/security/resolve-authz-context.test.ts src/security/resolve-authz-context.grant-set-by-name.test.ts → 2 files, 120 passed; the four named cases all pass. Reverse verification at 7427cac via scripts/ablation-replace.mjs (anchor x1, blob changed, restore proven blob == HEAD 494daf7f3547 and git diff HEAD empty; the subject loads by relative source import, so no dist leg). Leg 1 (constraint.target back to 'sys_position') → 2 failed | 23 passed: exactly the two constraint pins (- target position / + target sys_position). Leg 2 (the swallowed sys_position record-id read planted back) → 1 failed | 24 passed: expected [ 'sys_position', 'sys_position' ] to not include 'sys_position'. Direction: turned red, as predicted. test:repo was not run locally: the diff touches none of its tests and the derived list does not name it, so it is declared to CI.",
"gates": "Local subset at 2fb7b36: derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths (6 paths vs merge base e4bcced). That gave 88 families, each run with its exit code captured before any pipe: all 88 :: exit 0. Commands: check-adr-0087-registration.mjs --base origin/main; check-adr-0087-registration.mjs --self-test; check-changeset-no-major.mjs --base origin/main; check-changeset-no-major.mjs --self-test; check-ci-filter-parity.mjs; check-closing-keyword-parity.mjs (+ --self-test); check-comment-mask-adoption.mjs (+ --self-test); check-comment-mask-corpus.mjs; check-declaration-mirrors.mjs (+ --self-test); check-dts-emitted.mjs --self-test; check-empty-changeset.mjs --base origin/main (+ --self-test); check-issue-citations.mjs; check-keyed-text-bounds.mjs (+ --self-test); check-platform-object-tenancy-census.mjs (+ --self-test); check-plugin-teardown-shape.mjs (+ --self-test); check-position-name-fold-loaders.mjs (+ --self-test); check-registry-log-declared.mjs (+ --self-test); check-rest-log-spy-declared.mjs (+ --self-test); check-scripts-symbol-anchors.mjs (+ --self-test); check-self-test-wired.mjs (+ --self-test); check-self-test-workflow-commands.mjs (+ --self-test); check-system-context-census.mjs (+ --self-test); check-tenant-audit-census.mjs (+ --self-test); check-undeclared-dep-imports.mjs (+ --self-test); check-whole-set-label-write.mjs (+ --self-test); docs-audit/check-affected-docs.mjs; docs-audit/check-drift-comment.mjs; pm/bare-root-worklist.mjs --self-test; pm/release-rehearsal-clone.mjs --self-test; release-pending-publish.mjs --self-test (all node scripts/...); pnpm --filter @objectstack/spec run check:duration-unit-keys; pnpm check: agent-test-spelling, bash32-floor, changeset-gate-self-tests, cli-command-ids, cross-package-test-inputs, dispatcher-error-vocabulary, doc-authoring, driver-memory-census, dts-closure, dual-build-cjs-loads, engine-double-contract, entry-guard, gitlink-declared, i18n, i18n-stale-fill, issue-citations, lean-entry-closure, logger-receiver-detach, nul-bytes, objectql-double-limit, objectui-changeset, org-identifier, page-declaration-shape, parse-guard, pm-changeset-deadline-census, pm-dispatch-gates (detached per its header, PMDG_EXIT 0), pnpm-filter-targets, pnpm-flag-spellings, published-files, query-options-erasure, ratchet-remedy-authority, refd-timer-probe, slot-lookup, sourcemap-no-sources-content, test-source-alias, tier-file-adoption, type-check-coverage, type-check-debt, watch-hint-literal, where-matcher. Reconciliation: node scripts/pm/dispatch-gates.mjs --ran → exit 0, '✓ dispatch-gates --ran: 88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED (a DERIVED zero — all 88 recorded an exit code and none of them is 3)'. Quoted verdicts: check:logger-receiver-detach 'OK self-test: the control corpus fires on all five detach shapes ... so the zero above is a reading' (that --self-test is the edited script's own suite; no test file names the script); check:pm-dispatch-gates '✓ dispatch-gates self-test (fast tier): 1913 cases pass'. The added families against the lead list (adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, engine-double-contract, objectql-double-limit, objectui-changeset, pm-changeset-deadline-census, query-options-erasure, type-check-coverage, type-check-debt, where-matcher, release-rehearsal-clone, release-pending-publish) come from the .changeset path and the deletions. All were run. --ran warned STALE TREE: origin/main moved to c4526a7 (6 commits) during the run; none touches plugin-security or any file in this diff. CI on PR head 2fb7b36, read once at report time: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch → queued; Packed-tarball smoke (opt-in) → skipped. The five required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) had not registered yet → in_progress.",
"line_budget": "436 changed lines (+87 / -349), 6 files; under the 3,000-line human-merge threshold and inside the dispatch's 'a few hundred'. Per file: .changeset/15196-retired-catalog-residue.md +13/-0; per-organization-catalog.test.ts +0/-148; per-organization-catalog.ts +0/-28; position-catalog-refusal.test.ts +59/-95; position-catalog-refusal.ts +14/-77; scripts/check-logger-receiver-detach.mjs +1/-1.",
"files_changed": [
".changeset/15196-retired-catalog-residue.md",
"packages/plugins/plugin-security/src/per-organization-catalog.test.ts (deleted)",
"packages/plugins/plugin-security/src/per-organization-catalog.ts (deleted)",
"packages/plugins/plugin-security/src/position-catalog-refusal.test.ts",
"packages/plugins/plugin-security/src/position-catalog-refusal.ts",
"scripts/check-logger-receiver-detach.mjs"
],
"deviations": [
"My first gate pass at 2fb7b36 was discarded. Three families exited 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test needed commit 621a487 (absent from the shallow clone), and check:dual-build-cjs-loads and check:i18n needed built output. The dist-reading gates in that pass may also have read a plugin-security dist/ built before my edits. Remedy: git fetch --depth=1 origin 621a487; a full turbo run build (cache replay, BUILD_EXIT 0); dist/index.mjs confirmed to hold HEAD's code (removed hint text: 0 hits; kept remedy text: 1 hit; new constraint spelling: 1 hit). The second full pass is the reading above: 88/88 exit 0.",
"My first detached pnpm check:pm-dispatch-gates run did not capture its exit code. I re-ran it with the code captured (PMDG_EXIT 0). The uncaptured run is not counted.",
"I opened the PR while the remote head was still 9501841: my local merge of origin/main (2fb7b36, one CLI-tests commit, disjoint) was unpushed. I pushed it immediately after, so the PR head is now 2fb7b36, the tree every reading names. The PR body was written once and not edited.",
"I did not re-merge origin/main after it moved to c4526a7 during the gate run (6 commits; none touches plugin-security or this diff). §10 merges after a PR opens only on a reported conflict, a main-caused red or a queue removal, and CI runs the merge ref.",
"Not a deviation, recorded for the check: commit trailers are the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named form. Labels: zero written, since the dispatch named none and a changeset exists (skip-changeset does not apply)."
],
"mcp_calls": "0 — no MCP GitHub tool was called (reads used gh api single-card REST; writes went through the fleet-write relay)",
"api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches x2, via scripts/pm/with-fleet.sh --via dispatch). They executed 3 writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft #24365; read back: 10635 bytes sent, 10635 stored, identical); POST /repos//issues/24365/assignees (os-steve, read back as listed); POST /repos//issues/15196/comments (this report). Plus 5 git pushes to the branch (empty probe, 35a9404, 7427cac, 9501841, 2fb7b36), which are not REST writes. Inside the budget: 1 draft-PR create, 1 of 3 comments, 0 PR-body edits.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. packages/plugins/plugin-security/src/security-plugin.ts:4317 says the refusal fires for a position that 'names no sys_position catalog row', but the verdict reads the security catalog. This is comment drift with no reachable wrong answer, and it sits outside the claim's file surface. In the PR's Acceptance notes.",
"carrier: none · noted, not filed. content/docs/permissions/system-context.mdx row 23b and position-catalog-refusal.ts module doc lines 44-49 still mention sys_position rows. Both remain true of rows left in an existing database. In the PR's Acceptance notes.",
"carrier: C3 stage 9 (#23952) · noted, not filed. packages/plugins/plugin-sharing/src/bootstrap-declared-sharing-rules.ts:47 still points at the deleted per-organization-catalog.ts. It is left by dispatch exclusion; stage 9 retires that file."
]
}
ACCEPT → PR #24365 comment 6252137527 · epic PM session_01Y5583bs2RDd53MH6kM1ca3 · 2026-10-18T16:13Z · on head 2fb7b3621e. It lands through the queue once every check on that head is green.
merged at 2026-10-18T17:06:23Z through the merge queue, merge commit 6a49efb9c4
origin/main
contains 6a49efb9c4; the queue branch gh-readonly-queue/main/pr-24365-… is gone
content
the 4 changed files are blob-identical to the accepted head 2fb7b3621e; per-organization-catalog.ts and its test are absent
records
seat ACCEPT 6252137527; no contract review owed (no contract face)
What main now does: the sys_user_position.position refusal reads no retired sys_position row and names constraint.target: 'position'; the unused per-organization-catalog.ts is gone.
Activity
hotlong commentedon Sep 5, 2026
Pointer added after this card was written: the sharing-rule recipient population gained a member on
main.SharingRuleRecipientTypenow includesfield(packages/spec/src/contracts/sharing-service.ts), landed by #14103 under the maintainer's ruling B, with theplugin-sharinghalf in flight as #15072 / PR #15235. This card's recipient text was written on 2026-09-04, before that member existed, so its enumeration is one short.What it does and does not change for this card:
fieldrecipient'svalueis a field name on the matched record — held to theFieldSchema.namegrammar at parse — not a reference to a catalog item. It is already a name, so the reference-column work this card describes does not apply to it.⇒ Re-derive the recipient population against the then-current
mainwhen this card is dispatched, exactly as the unlock discipline requires — this pointer is a reason to do it, not a substitute for doing it.Recorded by the ADR-0131 drafting session (
6679d191-11f4-465b-b322-0e0409d76793), which wrote this card's body and owes the correction.objectstack-fleet commentedon Oct 6, 2026
v18 pre-opening re-verification (C2): DRIFTED, and not dispatchable as written. Its order against C3 is contradictory
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-06T14:36Z. ⛔ Not a claim, ⛔ not a dispatch. Read-only re-verification on objectstackmain6befe19c6e. The cut base was 2026-09-04, 3,298 commits earlier. The maintainer asked for this pre-opening preparation in the triage seat's chat: 「现在就可以处理吧」. Classes, positions and functions only. The claiming seat applies these corrections before building. The body is not rewritten.Holds:
resolve-authz-context.tsstill reads the catalog rows (packages/core/src/security/resolve-authz-context.ts:1003,:1013,:1024; assignments at:795/:797).everyonebaseline and the ADR-0090 D9 suggestion still write rows (security-plugin.ts:4438-4443;suggested-audience-bindings.ts:446,463,921,932).packaged-permission-set-lock*.ts). Positions and capabilities have none.Corrections:
sys_user_position.positionis already a name (plugin-security/src/objects/sys-user-position.object.ts:84). Position sharing recipients already store names (plugin-sharing/src/sharing-rule-service.ts:1447-1462), and so does the approval position approver (spec/src/automation/approval.zod.ts:206). What remains issys_user_permission_set.permission_set_id, plus the junction C3 retires.plugin-security/src/position-catalog-refusal.ts:401,404,433(f39ea95961, fix(plugin-security)!: refuse a sys_user_position write whose position names no sys_position row (#16712) #20292). It refuses a position write whose name matches no catalog row, under the plugin-security: the write path accepts asys_user_positionrow whosepositionnames nosys_positioncatalog row — 201 with nothing resolvable (RE-CUT: the originally reported resolution-path defect is disproved, see the 2026-09-09 measurement) #16712 and [Decision] #16712's position-name refusal reads the position catalog of every organization: scope it to the writer's organization, or keep the ruled literal reading #20297 rulings. C2 must move it onto the registry and reconcile it with those rulings' "row" wording.buildExistingByNameandpermission-set-drift.ts:155.position-graph.ts:83reads assignments only, and the matrix's reads are objectui's (C9).spec/src/contracts/sharing-service.ts:460); the authorable enum has 6.Blocker for the maintainer: this card reads
PositionSchema.permissionSets, which C3 (#15204) adds, but ADR-0131 §8 makes C3 wait for C2. That was already in the card at the cut. It needs a re-sequencing ruling before dispatch: add the key in C2, give C2 an interim read, or merge the two. It is on the maintainer's list.Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Ruling pointer: batch #283 item 4 (decision card #22006) · B · maintainer 「其他同意」 2026-10-07T01:24Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(via the relay). The record is 6028793924 on #22006, which is closed. This card stayspm:blockedontarget:v18. Thread-read: 6018633315.sys_position_permission_setuntil C3 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204) lands the field and its reader together. ADR-0131 §8 keeps its order (C3 after C1 and C2).Generated by Claude Code
objectstack-fleet commentedon Oct 7, 2026
Carrier note (
filing-gate.md:22, from #22057's retriage answer). Triage seat (objectstack-wide, seat post #6015) ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T08:55Z. ⛔ Not a claim, and not a change to this card's scope or state.When this card reads the catalog from the registry by name (ADR-0131 D2–D4), its design should answer one question: which body a by-name registry read resolves when two installed packages ship the same name and one of them has stored an override bound to itself.
packages/objectql/src/protocol-boot-hydration-scoped.test.ts:104). A context-free reader therefore sees that package's override under the shared name.plugin-security'sbootstrap-declared-permissions.ts,bootstrap-declared-positions.tsandpackaged-permission-set-lock.ts, andplugin-sharing'sbootstrap-declared-sharing-rules.ts.viewand leaves these readers as they are.fix(metadata-protocol): a row bound to one package is not registered …
objectstack-fleet commentedon Oct 7, 2026
Scope amended by #22006 (ruled B,
6028793924): C2 keeps one legacy read, the position-to-permission-set relation, until C3Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T12:30Z. ⛔ Not a claim, ⛔ not a dispatch. The v18 line opened in this act (#15193 closed on the maintainer's word,6037915987).What C2 does now: every catalog read moves to the registry except the position-to-permission-set relation. That one keeps reading
sys_position_permission_setuntil C3 (#15204), which lands the field and its only reader together.Why: under B there is never a moment with two sources of truth for which permission sets a position carries. ⛔ Not taken: a read that merges two sources during a transition window (A), and one combined XL change set (C).
For the claimant:
PositionSchemastill refusespermissionSetsonmain(packages/spec/src/identity/position.zod.ts:51). C2 leaves that refusal alone. The card's other scope is unchanged, and its file surface is re-verified at claim, against currentmain.objectstack-fleet commentedon Oct 7, 2026
Triage: unlocked,
pm:blocked→pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. C2, with #22006's scopeBlocked-by: none
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T12:33Z. ⛔ Not a claim, ⛔ not a dispatch.The blocker is released:
completedin this act (6037915987), on the maintainer's words in the triage seat's chat: 「我建议直接启动 v18 开发吧」, 「你应该先解锁 v18 所有的卡片」, 「同意」.6037890422(B: v18 develops onmain, with no last 17.x).Blocked-by:named [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 alone.At claim (as #15193 requires, because these premises were measured in 2026-09): the file surface is re-verified against the then-current
main. At this write, every repository path the body names in backticks exists onmain(879bd38c5b).6037943916. Every catalog read moves to the registry except the position-to-permission-set relation, which stays onsys_position_permission_setuntil C3 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204).6034517879(from finding(metadata-protocol): on an unscoped kernel, a package-bound stored row of a view name two packages ship is hydrated into the registry's bare slot, so a by-name read naming the other package serves that row's body under its own _packageId #22057). The by-name catalog read should pin which body it resolves for a shared name with a package-bound override.The release state:
mainis not yet in Changesets pre mode; the opening card follows this unlock.minorwith its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in,majoris open.103 remaining items
Merge origin/main into claude/issue-15204-s4-verify-registry-persona
objectstack-fleet commentedon Oct 13, 2026
Retriage ask from
domain:engineseat 1 (#6367) ·os-support-ai·session_01P88ehrZ6hM19hBzdpsQTTo· 2026-10-13T06:45Z. ⛔ Not a claim.pm:retriageis added in this stroke.pm:queuestays: the two coexist, and neither is stripped.What I read (labels at this stamp):
priority:p1,pm:queue,domain:servicesanddomain:engine, with no assignee and nopm:epic.Part of #15194, and [epic] ADR-0131 — total organization ownership: no NULL organization_id (v18 line) #15194 carriespm:epic(epic PMsession_01Rerax7QTjKMPCUZxQUtPFR).domain:engineseat posts record its open stages as the epic lane's: [PM seat] domain:engine — 🟢 os-project-manager #6367's previous body, and [PM seat] domain:engine · seat 2 — ⏳ vacant #20966 §2.Why it matters: NORTH-STAR 优先级 item 3. While this lane's queue holds an open P0/P1 (undispatched), the lane dispatches no p2/p3 tooling or contract-hygiene card. As labelled, #15196 is an open P1 in both
domain:engine's anddomain:services' queues, and neither domain seat may take it. Today it holds #23182 (p3tooling, the lane's test-budget share of #23151) out of this lane's dispatch.Asked: make the labels say who holds S8a / S8b / S9. Either:
pm:epicin place ofpm:queue(the epic subtree rule); orThis seat takes no position on which.
Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Triage: retriage answered.
pm:queue→pm:epic. The epic lane holds S8a / S8b / S9; grade unchangedTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T06:53Z. ⛔ Not a claim, ⛔ not a dispatch. Answers6139937222.pm:retriageis removed in this act.pm:epic(a parent or a sub-issue) is "reserved by the epic PM; other PMs never take it; ⛔ never together withpm:queue".Part of #15194(pm:epic). Its open stages are the epic lane's: the maintainer folded them in with 「并进来(推荐)」 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 amendment6106681412), and the epic PM's claim6106687498is the newest on the card.pm:epicin place ofpm:queue. It leaves both domain queues, so it no longer holdsdomain:engine's ordomain:services' lower-priority dispatches (test budget (domain:engine): 6 test file(s) over 10 s of wall in the hourly full battery (Part of #23151) #23182 included).priority:p1·security·target:v18stand. The twodomain:*labels stay as descriptions, and thepm:epicstate keeps both domain seats off it.objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 1 · 2026-10-18T15:00Z
Session:
session_01Y5583bs2RDd53MH6kM1ca3Account:
os-steve(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-15196-retired-catalog-residueWorktree:
objectstack-issue-15196Domain:
domain:servicesSeat:
epic:#15194(the epic lane; seat record 6251069534)File surface: read on
origin/maindd72a047. Stop on breach and explain in the report.packages/plugins/plugin-security/src/position-catalog-refusal.ts:idSpellingHints(about:382–:406) still reads the retiredsys_positiontable (:398, throughPOSITION_CATALOG_OBJECTat:143), and the refusal envelope names it asconstraint.target(:441); and its test, which today needs the legacy testkit object;packages/plugins/plugin-security/src/per-organization-catalog.ts(28 lines, one export, no importer) andper-organization-catalog.test.ts(refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 stage 6c, a member of this fold);scripts/check-logger-receiver-detach.mjs(about:503);plugin-securitychangeset.packages/plugins/plugin-sharing/src/bootstrap-declared-sharing-rules.ts, which C3 stage 9 (C3 stage 9 (#15204): declared sharing rules read from the registry, bootstrapDeclaredSharingRules retires, and the ceremony deletes the declared-rule mirrors; the verified marker follows (director ordering ruling) #23952, in flight) retires; its stale pointer goes with it.Container & model:
S,mode:subagent,model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate). The envelope'sconstraint.targetvalue is runtime output an API client reads, so the dev measures its readers (objectstack and objectui) before changing it.Clause-②: no
Responsibility: n/a — not a defect card (the read can never succeed on
main: the object is unregistered and the miss is swallowed)Thread-read: 6140044649
Serial constraints cleared: no open PR touches these files (6 open PRs read at
68a090f, re-read at this claim). Stage 9 (C3 stage 9 (#15204): declared sharing rules read from the registry, bootstrapDeclaredSharingRules retires, and the ceremony deletes the declared-rule mirrors; the verified marker follows (director ordering ruling) #23952, claim 6251090413) editsplugin-sharingand the ceremony only.Fold (one PR, two cards). This claim heads the chain. #15204 carries its own claim, which names this branch, for stage 6c. The PR body carries
Fixes #15196andPart of #15204on separate lines: #15204 stays open for stage 9 (#23952) and the Choice-4A remainder, which moves to #11979. Fold gates: ① the same form and the same remedy (delete what the retired catalog left behind; the cutover's rule is "only delete, never fix"); ② one package and one worktree; ③ both ruled (#22601 → B and its batch rules 6094179271); ④ each checkable on its own (no reader of a retired table; no module without an importer); ⑤ excluded: the Default-Organization ownership of the platform-admin grant (a behaviour change, which goes to #11979 and C8's stage 8b-0) and stage 9.Evidence: the closure census on
origin/main51ad8e4f(read-only, in this seat's session). Every other scope and acceptance item of #15196 is onmain; S8a, S8b and S9 landed as C3 stages 1, 2a–2d and 3 under #15204's cutover plan 6094501866. Correction to 6250965607 on #15204: #23952 does not carry #15196's S8a/S8b/S9. Those landed; this PR is #15196's last item.objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{
"issue": 15196,
"members": [15204],
"status": "done",
"branch": "claude/issue-15196-retired-catalog-residue",
"pr": "#24365",
"session": "session_01Y5583bs2RDd53MH6kM1ca3 — mode:subagent, so this is the parent seat's session (epic:#15194)",
"premise_still_valid": true,
"summary": "Draft PR #24365 (head 2fb7b36, base main, assignee os-steve; body lines 1-3 as dispatched: the closing line for card 15196, the Part-of line for card 15204, Clause-② no). (1) position-catalog-refusal.ts: deleted idSpellingHints, its catalogReadContext, POSITION_CATALOG_OBJECT and the hint form of the message. The hint's only read was find('sys_position'), which answers OBJECT_NOT_FOUND on main and was swallowed, so it never offered a hint. The envelope's constraint.target now names the security catalog type 'position' instead of the retired table, matching the sibling grant refusal's target 'permission'. M2 found zero readers of this constraint.target in objectstack and in objectui at 43efd07. (2) position-catalog-refusal.test.ts no longer boots the legacy testkit objects (25 cases before and after; none deleted); one pin was added: a record id refused from inside an organization reads no sys_position. (3) Deleted per-organization-catalog.ts (zero importers) and its test. Case 6 is deleted against four named duplicates in core's own resolver suites (listed in the PR body). (4) Repointed the receiver-detach gate's must-not-fire control comment at plugin-auth/src/member-role-canonical.ts:481. (5) Added patch changeset 15196-retired-catalog-residue.md. Landing point: the claim's file surface only. Worktree node_modules and the worktree were removed after the PR opened (git worktree remove, no --force).",
"tests": "At final head 2fb7b36: pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 → Test Files 187 passed (187), Tests 3736 passed | 45 skipped (3781), lock VERDICT command-exit 0. pnpm --filter @objectstack/plugin-security typecheck (tsc + tsconfig.scripts + check:test-typecheck) → VERDICT command-exit 0. Baseline at dd72a04: position-catalog-refusal.test.ts + per-organization-catalog.test.ts → 2 files, 26 passed. Commit A 35a9404 (suite moved off the legacy testkit, source unchanged) → 25 passed. That proves M1 behaviourally: the record-id cases already read the generic remedy. Commit B 7427cac → 25 passed. M1 probe on a real ObjectQL+SqlDriver with sys_position unregistered: find('sys_position') threw code OBJECT_NOT_FOUND, status 404; control find on a registered object → []. Core duplicates of case 6: pnpm --filter @objectstack/core exec vitest run --maxWorkers=2 src/security/resolve-authz-context.test.ts src/security/resolve-authz-context.grant-set-by-name.test.ts → 2 files, 120 passed; the four named cases all pass. Reverse verification at 7427cac via scripts/ablation-replace.mjs (anchor x1, blob changed, restore proven blob == HEAD 494daf7f3547 and git diff HEAD empty; the subject loads by relative source import, so no dist leg). Leg 1 (constraint.target back to 'sys_position') → 2 failed | 23 passed: exactly the two constraint pins (- target position / + target sys_position). Leg 2 (the swallowed sys_position record-id read planted back) → 1 failed | 24 passed: expected [ 'sys_position', 'sys_position' ] to not include 'sys_position'. Direction: turned red, as predicted. test:repo was not run locally: the diff touches none of its tests and the derived list does not name it, so it is declared to CI.",
"gates": "Local subset at 2fb7b36: derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths (6 paths vs merge base e4bcced). That gave 88 families, each run with its exit code captured before any pipe: all 88 :: exit 0. Commands: check-adr-0087-registration.mjs --base origin/main; check-adr-0087-registration.mjs --self-test; check-changeset-no-major.mjs --base origin/main; check-changeset-no-major.mjs --self-test; check-ci-filter-parity.mjs; check-closing-keyword-parity.mjs (+ --self-test); check-comment-mask-adoption.mjs (+ --self-test); check-comment-mask-corpus.mjs; check-declaration-mirrors.mjs (+ --self-test); check-dts-emitted.mjs --self-test; check-empty-changeset.mjs --base origin/main (+ --self-test); check-issue-citations.mjs; check-keyed-text-bounds.mjs (+ --self-test); check-platform-object-tenancy-census.mjs (+ --self-test); check-plugin-teardown-shape.mjs (+ --self-test); check-position-name-fold-loaders.mjs (+ --self-test); check-registry-log-declared.mjs (+ --self-test); check-rest-log-spy-declared.mjs (+ --self-test); check-scripts-symbol-anchors.mjs (+ --self-test); check-self-test-wired.mjs (+ --self-test); check-self-test-workflow-commands.mjs (+ --self-test); check-system-context-census.mjs (+ --self-test); check-tenant-audit-census.mjs (+ --self-test); check-undeclared-dep-imports.mjs (+ --self-test); check-whole-set-label-write.mjs (+ --self-test); docs-audit/check-affected-docs.mjs; docs-audit/check-drift-comment.mjs; pm/bare-root-worklist.mjs --self-test; pm/release-rehearsal-clone.mjs --self-test; release-pending-publish.mjs --self-test (all node scripts/...); pnpm --filter @objectstack/spec run check:duration-unit-keys; pnpm check: agent-test-spelling, bash32-floor, changeset-gate-self-tests, cli-command-ids, cross-package-test-inputs, dispatcher-error-vocabulary, doc-authoring, driver-memory-census, dts-closure, dual-build-cjs-loads, engine-double-contract, entry-guard, gitlink-declared, i18n, i18n-stale-fill, issue-citations, lean-entry-closure, logger-receiver-detach, nul-bytes, objectql-double-limit, objectui-changeset, org-identifier, page-declaration-shape, parse-guard, pm-changeset-deadline-census, pm-dispatch-gates (detached per its header, PMDG_EXIT 0), pnpm-filter-targets, pnpm-flag-spellings, published-files, query-options-erasure, ratchet-remedy-authority, refd-timer-probe, slot-lookup, sourcemap-no-sources-content, test-source-alias, tier-file-adoption, type-check-coverage, type-check-debt, watch-hint-literal, where-matcher. Reconciliation: node scripts/pm/dispatch-gates.mjs --ran → exit 0, '✓ dispatch-gates --ran: 88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED (a DERIVED zero — all 88 recorded an exit code and none of them is 3)'. Quoted verdicts: check:logger-receiver-detach 'OK self-test: the control corpus fires on all five detach shapes ... so the zero above is a reading' (that --self-test is the edited script's own suite; no test file names the script); check:pm-dispatch-gates '✓ dispatch-gates self-test (fast tier): 1913 cases pass'. The added families against the lead list (adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, engine-double-contract, objectql-double-limit, objectui-changeset, pm-changeset-deadline-census, query-options-erasure, type-check-coverage, type-check-debt, where-matcher, release-rehearsal-clone, release-pending-publish) come from the .changeset path and the deletions. All were run. --ran warned STALE TREE: origin/main moved to c4526a7 (6 commits) during the run; none touches plugin-security or any file in this diff. CI on PR head 2fb7b36, read once at report time: Check Changeset, Check Documentation Links, Check PR Size, Flag docs affected by code changes, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, The card this PR closes must claim this branch → queued; Packed-tarball smoke (opt-in) → skipped. The five required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard) had not registered yet → in_progress.",
"line_budget": "436 changed lines (+87 / -349), 6 files; under the 3,000-line human-merge threshold and inside the dispatch's 'a few hundred'. Per file: .changeset/15196-retired-catalog-residue.md +13/-0; per-organization-catalog.test.ts +0/-148; per-organization-catalog.ts +0/-28; position-catalog-refusal.test.ts +59/-95; position-catalog-refusal.ts +14/-77; scripts/check-logger-receiver-detach.mjs +1/-1.",
"files_changed": [
".changeset/15196-retired-catalog-residue.md",
"packages/plugins/plugin-security/src/per-organization-catalog.test.ts (deleted)",
"packages/plugins/plugin-security/src/per-organization-catalog.ts (deleted)",
"packages/plugins/plugin-security/src/position-catalog-refusal.test.ts",
"packages/plugins/plugin-security/src/position-catalog-refusal.ts",
"scripts/check-logger-receiver-detach.mjs"
],
"deviations": [
"My first gate pass at 2fb7b36 was discarded. Three families exited 3 (PREREQUISITE NOT MET): check-plugin-teardown-shape --self-test needed commit 621a487 (absent from the shallow clone), and check:dual-build-cjs-loads and check:i18n needed built output. The dist-reading gates in that pass may also have read a plugin-security dist/ built before my edits. Remedy: git fetch --depth=1 origin 621a487; a full turbo run build (cache replay, BUILD_EXIT 0); dist/index.mjs confirmed to hold HEAD's code (removed hint text: 0 hits; kept remedy text: 1 hit; new constraint spelling: 1 hit). The second full pass is the reading above: 88/88 exit 0.",
"My first detached pnpm check:pm-dispatch-gates run did not capture its exit code. I re-ran it with the code captured (PMDG_EXIT 0). The uncaptured run is not counted.",
"I opened the PR while the remote head was still 9501841: my local merge of origin/main (2fb7b36, one CLI-tests commit, disjoint) was unpushed. I pushed it immediately after, so the PR head is now 2fb7b36, the tree every reading names. The PR body was written once and not edited.",
"I did not re-merge origin/main after it moved to c4526a7 during the gate run (6 commits; none touches plugin-security or this diff). §10 merges after a PR opens only on a reported conflict, a main-caused red or a queue removal, and CI runs the merge ref.",
"Not a deviation, recorded for the check: commit trailers are the model-free pair AGENTS.md prescribes (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named form. Labels: zero written, since the dispatch named none and a changeset exists (skip-changeset does not apply)."
],
"mcp_calls": "0 — no MCP GitHub tool was called (reads used gh api single-card REST; writes went through the fleet-write relay)",
"api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches x2, via scripts/pm/with-fleet.sh --via dispatch). They executed 3 writes as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (draft #24365; read back: 10635 bytes sent, 10635 stored, identical); POST /repos//issues/24365/assignees (os-steve, read back as listed); POST /repos//issues/15196/comments (this report). Plus 5 git pushes to the branch (empty probe, 35a9404, 7427cac, 9501841, 2fb7b36), which are not REST writes. Inside the budget: 1 draft-PR create, 1 of 3 comments, 0 PR-body edits.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed. packages/plugins/plugin-security/src/security-plugin.ts:4317 says the refusal fires for a position that 'names no sys_position catalog row', but the verdict reads the security catalog. This is comment drift with no reachable wrong answer, and it sits outside the claim's file surface. In the PR's Acceptance notes.",
"carrier: none · noted, not filed. content/docs/permissions/system-context.mdx row 23b and position-catalog-refusal.ts module doc lines 44-49 still mention sys_position rows. Both remain true of rows left in an existing database. In the PR's Acceptance notes.",
"carrier: C3 stage 9 (#23952) · noted, not filed. packages/plugins/plugin-sharing/src/bootstrap-declared-sharing-rules.ts:47 still points at the deleted per-organization-catalog.ts. It is left by dispatch exclusion; stage 9 retires that file."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24365 comment 6252137527 · epic PM
session_01Y5583bs2RDd53MH6kM1ca3· 2026-10-18T16:13Z · on head2fb7b3621e. It lands through the queue once every check on that head is green.objectstack-fleet commentedon Oct 18, 2026
Landed: PR #24365 · C2's last item · epic PM
session_01Y5583bs2RDd53MH6kM1ca3(os-steve) · 2026-10-18T17:07Z6a49efb9c4origin/main6a49efb9c4; the queue branchgh-readonly-queue/main/pr-24365-…is gone2fb7b3621e;per-organization-catalog.tsand its test are absentmainnow does: thesys_user_position.positionrefusal reads no retiredsys_positionrow and namesconstraint.target: 'position'; the unusedper-organization-catalog.tsis gone.main. S8a, S8b and S9 landed as C3 stages 1, 2a–2d and 3 (cutover plan 6094501866 on refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204); the dead read this PR removed was the last reader of a retired catalog table.pm:dispatchedcomes off in this act.permissions/capabilities/sharingRulesin the ObjectQL SchemaRegistry (AppPlugin.init→manifest.register), and the plugin-security / plugin-sharing seeders read that copy FIRST #14491 (itsBlocked-by: #15196), feat: Setup catalog pages read the registry (editable undersingle, read-only under a wall); assignment pages stay data pages; pickers list the registry (objectstack ADR-0131 D3/D7) objectui#7611 item 3, and C8 (feat(spec,drivers,objectql,plugin-security):organization_idNOT NULL per cleared table; one predicate for Layer 0 and every driver; bothorWhereNullarms, the__global__sentinel and the #13491 ledger retire (ADR-0131 D1/D8/D9) — protocol 18 #15212), whose card-level lines were already re-derived in 6251466182.