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
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 4, 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:
- ⛔ Not an id→name rewrite target. A
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. ⚠️ 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
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.- ⛔ Not an id→name rewrite target. A
objectstack-fleet commented
on Oct 6, 2026 ContributorMore actionsv18 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).- The
everyonebaseline 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.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. - The reader census was never attached. An approximate re-count: 22 files / 62 read sites, up from 21/59 at the cut.
- New:
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. - Helper reads the regex misses:
buildExistingByNameandpermission-set-drift.ts:155. - The claim attaches a real census.
- New:
- Drop "hierarchy security" and "permission matrix" as server-side reader families.
position-graph.ts:83reads 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.
Generated by Claude Code
objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsRuling 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.- C2 keeps ONE legacy read. Every catalog read moves to the registry except the position-to-permission-set relation, which keeps reading
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). - Not taken: A (the field in C2 with a merged two-source read during the transition) and C (C2 and C3 as one XL change set).
- Card face: the triage seat carries this into the card at the v18 re-verification; the ruling itself is the record above.
Generated by Claude Code
- C2 keeps ONE legacy read. Every catalog read moves to the registry except the position-to-permission-set relation, which keeps reading
objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsCarrier 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.
- Today, the bound row holds the bare entry, as loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 pins (
packages/objectql/src/protocol-boot-hydration-scoped.test.ts:104). A context-free reader therefore sees that package's override under the shared name. - Readers in scope: the declared-metadata bootstraps of this card's types,
plugin-security'sbootstrap-declared-permissions.ts,bootstrap-declared-positions.tsandpackaged-permission-set-lock.ts, andplugin-sharing'sbootstrap-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.
- PR fix(metadata-protocol): a row bound to one package is not registered under a name another package ships #22066 (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) keeps its skip to
viewand leaves these readers as they are.
- Today, the bound row holds the bare entry, as loadMetaFromDb boot hydration keeps a third inline copy of the overlay→registry rule with an UNSCOPED artifact lookup (ADR-0048 gap) #4624 pins (
- added a commit that references this issue
on Oct 7, 2026 objectstack-fleet commented
on Oct 7, 2026 ContributorMore actionsScope 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 commented
on Oct 7, 2026 ContributorMore actionsTriage: 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:
- [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 closed
completedin this act (6037915987), on the maintainer's words in the triage seat's chat: 「我建议直接启动 v18 开发吧」, 「你应该先解锁 v18 所有的卡片」, 「同意」. - The ruling record is decision: open v18 now and ship it in stages — release the last 17.x from main first without waiting for #21908's deny (A), skip the last 17.x (B), or keep #22009's order (C)? #22050
6037890422(B: v18 develops onmain, with no last 17.x). - This card's
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).- Scope: read decision: ADR-0131 C2/C3 order — C2 must read positions' permission sets, which only C3 adds, while §8 makes C3 wait on C2. Which way is the knot cut? #22006's ruling, written here at
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). - Also on this thread: the carrier note
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.- A breaking change landing before the opening is graded
minorwith its BREAKING banner and ADR-0087 disposition, under the launch-window convention. Once pre mode is in,majoris open. - ⛔ chore: version packages #21988 is not merged.
- [gate] the v18 development line is not open — ADR-0131 execution cards are blocked on this card #15193 closed
85 remaining items
objectstack-fleet commented
on Oct 9, 2026 ContributorMore actionsLanded (stage) and released ·
domain:engine#2·session_01Bw3y2DWhT9RPnrmDsNqEVG(os-tesla) · 2026-10-09T15:42Z.- C2 stage S5a: PR feat(core,plugin-security,plugin-auth)!: the authorization resolver reads a grant's permission set by name (ADR-0131 D4, C2 stage S5a) #22495 merged through the queue as
2e10c9abe0, read onorigin/main. It isPart of #15196, and this card stays open. Closing-keyword check: PR feat(core,plugin-security,plugin-auth)!: the authorization resolver reads a grant's permission set by name (ADR-0131 D4, C2 stage S5a) #22495 carries no closing keyword, and the card is still open. - Content on
origin/main:readGrantSetRowsByName(3) andgrantSetNameOf(4) are inpackages/core/src/security/resolve-authz-context.ts.- The organization rule is in
plugin-security'sgrant-permission-set-name.ts. .changeset/15196-s5a-resolver-grant-name.mdis present. The queue branch is gone.
- Merged at 2026-10-09T15:41Z.
- What landed (
@objectstack/core,plugin-security,plugin-auth,minor, BREAKING narrowing):- The resolver reads a user grant's set by name: the grant's own organization's row, else the organization-less row. A grant naming nothing or another organization's set confers nothing.
- Platform standing comes only through the organization-less
admin_full_accessrow. - The S4a hook applies the S4b rule at write time.
- The last-administrator guard treats
organization_idonsys_permission_setas a standing column. - Both S5b prerequisites hold: the hook rule, and the upgrade-boot window, which is accounted for by measured boot ordering.
- Records: ACCEPT 6083715140. No contract review was owed (no contract surface touched).
- What remains, and its carriers:
- S5c (
domain:cli). - S8a / S8b, which also carry S5a's answered question A: the by-name rule's three internal copies become one
@objectstack/coreexport, imported byplugin-securityandplugin-auth. They also carry the items the S7 release lists (6080018188). - S8b / S9: the backfill failure warning now also means "confers nothing until then".
- S9: a grant on a catalog-unresolved row-only set stays unnamed and confers nothing; the boot report lists it.
- S10: as the S7 release lists.
- S5c (
- Released:
pm:dispatched→pm:queue, assignee cleared. This seat's S5a claim6080695984is closed. The dev session is archived.
Release:
session_01Bw3y2DWhT9RPnrmDsNqEVG(domain:engineseat 2) · stage S5a delivered (PR #22495,2e10c9abe0) · back to the queue for the next stage's lane.- C2 stage S5a: PR feat(core,plugin-security,plugin-auth)!: the authorization resolver reads a grant's permission set by name (ADR-0131 D4, C2 stage S5a) #22495 merged through the queue as
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsClaim: PM loop round 1 · 2026-10-10T01:21Z
Session:session_013j5gkUCpqQiti4GgPqqmnt
Account:zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-15196-s10-position-namespace
Worktree:objectstack-issue-15196
Domain:domain:services
Seat:domain:services#1(seat post #6021)
Provenance:- Claimed from
pm:queueafterdomain:engineseat 2's release6084206172(S5a landed,2e10c9abe0). This claim covers stage S10 only ("one namespace for positions", census6038897018: lanedomain:services, "any time after S1", about 250 lines). - S5c (
domain:cli), S8a (domain:engine), S8b and S9 are not claimed here. S8b waits on the same-name rule the S7 release carries (6075437096). - The S7 release names S10's first step (
6080018188): measure first whether feat(objectql,metadata,runtime)!: refuse a package whose position, permission set or capability name is already held by an installed package, the environment catalog or a built-in (ruling Q4 = A on #15196; narrows ADR-0048 §3.4) #22135's item seam already refuses a Setup position under a package-held name.
File surface:packages/plugins/plugin-security/only, read onorigin/main6a3f82efa7: - Measure first, per posture (
single,group, a walledisolated), and record each answer:- what a Setup create or rename of a
sys_positionrow answers when the name is held by a package or a built-in; - what the metadata door answers the same name.
The S7 write-through (position-write-through.ts, about:42–:57) says it stands down for such a name and lets the row write proceed, while the metadata door refuses with403 NOT_OVERRIDABLE.
- what a Setup create or rename of a
- If the data door admits it: the
sys_positionwrite path refuses a create or rename to a name a package or a built-in holds, through the provenance read the write-through's stand-down already uses (SchemaRegistry.getArtifactItem). ⛔ No second predicate. The landing site is the module that runs onsys_positionwrites (position-write-through.ts, or a sibling registered fromsecurity-plugin.tsbeside it), the dev's measured choice. - Tests in
plugin-security, covering the census pins: the refusal envelope'scodeandstatus(no message pin), with the ablation (remove the check → the write lands → red). .changeset/15196-s10-position-namespace.md:@objectstack/plugin-security, BREAKING narrowing, with the FROM → TO and the one-line remedy (rename).- If the measurement shows the door already refuses in every posture: no code. The report carries the readings, and the stage closes on them.
- ⛔ No
packages/spec, noobjectql/ metadata-door change (stop and report if the Studio half needs one), and no new error code (a new code isClause-②: yes, which is spec-lane work: reuse a registered one, and name it). ⛔ Not the walled half (a later stage's, per the write-through's own note). ⛔ No S8 reader switch. - Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; the default tier, a measure-first security stage)
Clause-②: no (narrowing) - A Setup write that the data door answers today may be refused. That narrows an accepted input, and no input widens. Under
dispatch-gates' clause-② floor it owes one contract-review-tier review before the queue.
Responsibility: n/a — not a defect card
Thread-read: 6084206172
Governing text: ADR-0048 addendum (2026-10-08): positions hold one name per deployment (N.2). Its N.3 keeps a metadata author's save under ADR-0005 overlay precedence, and positions areallowOrgOverride: false, so the door refuses such a save. ADR-0131 D3: a definition lives only in the environment registry.
Serial constraints cleared: - security(explain):
explaintransfer composes row-level security as a read (mapOperationToRLS→select), while the door composes it as an update — an update policy that refuses thePATCH owner_idis reported as not applying #22550 (domain:servicesseat 2, in flight, no PR yet) editssecurity-plugin.ts's step 2.7 pre-image gate region andexplain-engine.ts, and adds a lifecycle-verb helper. Region-level only: whichever lands later mergesmain. - No open PR touches
plugin-security(9 open non-release PRs read at their file lists, 2026-10-10T01:21Z). - plugin-approvals:
sys_approval_requesthas no parent-record read gate on the data door, and the ruled record-reader tier cannot be switched on by an app (17.7.0) #22559 and plugin-approvals: amanagerapprover whose owner has nomanager_idlands as the literalmanager:undefinedinpending_approvers, where the documented contract says the request opens with an empty slate (17.7.0) #22558 (this seat, in flight) are inplugin-approvals: disjoint.
Generated by Claude Code
- Claimed from
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsos-dev-report
{
"issue": 15196,
"stage": "S10",
"status": "done",
"branch": "claude/issue-15196-s10-position-namespace",
"pr": "#22582",
"session": "session_013j5gkUCpqQiti4GgPqqmnt — this run's harness-stamped id (subagent: the parent's)",
"premise_still_valid": true,
"summary": "Measured first, per posture and door, on the real showcase composition (local dogfood probe, deleted). Studio PUT /meta/position/NAME already answered 403 NOT_OVERRIDABLE for a package-held or built-in name in single, group and isolated. The Setup data door admitted only under single: a create of 'manager' (package), 'everyone' or 'guest' (built-in anchors) answered 201 and landed a second same-name row in the Default Organization. A rename into 'exec' or 'guest' answered 200, and it also deleted the renamed position's environment definition. 'org_admin' was already 400 VALIDATION_FAILED (engine rule). Under group and isolated, every organization holds its own seeded rows, so the same writes answer 409 UNIQUE_VIOLATION (unchanged, the walled half is out of scope). Fix in position-write-through.ts: under single, after the engine accepts the row, a create or a rename into a name asks the metadata door's own verdict, ObjectStackProtocolImplementation.packagedBaseRefusal (same predicate as saveMetaItem: getArtifactItem; same emitter). The door's refusal is relayed verbatim (403 NOT_OVERRIDABLE, no new code), and the S7 undo removes or restores the row. An edit that keeps such a name and a delete still stand down (Q2 = A). Engine refusals keep precedence. A door without the verdict keeps the S7 stand-down. Hypothesis 3: stack-declared positions now carry _packageId com.example.showcase (PR #22262, 0b997ea, closed the carrier after 6040687107), and built-ins carry com.objectstack.plugin-security. Both are refused alike. #15196 stays open (PR body line 1 'Part of #15196').",
"tests": "plugin-security full suite @ start 01:49:31Z: 192 files passed, 4044 passed | 45 skipped, exit 0; position-write-through.test.ts re-run at final head ecf9164: 41/41, exit 0; plugin-security typecheck exit 0 (tsc, tsconfig.scripts, check:test-typecheck 0 errors). HTTP probe at ecf9164 (after a plugin-security rebuild): single create manager/everyone/guest 403 NOT_OVERRIDABLE, rename into exec/guest 403 NOT_OVERRIDABLE, org_admin 400 VALIDATION_FAILED, fresh name 201; group and isolated unchanged at 409 UNIQUE_VIOLATION. ABLATION (fix committed first, 91177a2; scripts/ablation-replace.mjs; anchor = the heldPositionNameRefusal guard line, replaced by one that returns null for every real name, marker S10_ABLATION_MARKER). Leg 1, unit (subject imported from src, no dist on the path): anchor x1->x0, blob 1acd4313e68c->ba75c2f33e43, 7 failed | 33 passed (40), exactly the 7 S10 refusal pins red and the 3 controls green; restore blob == HEAD and git diff HEAD empty. Leg 2, dist/HTTP: mutated build exit 0; ablation-dist-preflight marker present in dist/index.js and dist/index.mjs (exit 0); showcase probe back to the pre-S10 answers (create manager 201, everyone 201, rename exec 200, guest 200, the renamed definition deleted); restore proven; rebuild; --absent preflight dist absent. The tree reading first answered exit 3 because the untracked local probe file was still in the tree; after the probe was deleted the rerun answered exit 0 (dist absent, tree clean).",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main -> exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test -> exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main -> exit 0",
"node scripts/check-changeset-no-major.mjs --self-test -> exit 0",
"node scripts/check-ci-filter-parity.mjs -> exit 0",
"node scripts/check-closing-keyword-parity.mjs -> exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test -> exit 0",
"node scripts/check-comment-mask-adoption.mjs -> exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test -> exit 0",
"node scripts/check-comment-mask-corpus.mjs -> exit 0",
"node scripts/check-dts-emitted.mjs --self-test -> exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main -> exit 0",
"node scripts/check-empty-changeset.mjs --self-test -> exit 0",
"node scripts/check-issue-citations.mjs -> exit 0",
"node scripts/check-keyed-text-bounds.mjs -> exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test -> exit 0",
"node scripts/check-platform-object-tenancy-census.mjs -> exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test -> exit 0",
"node scripts/check-plugin-teardown-shape.mjs -> exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test -> exit 0",
"node scripts/check-registry-log-declared.mjs -> exit 0",
"node scripts/check-registry-log-declared.mjs --self-test -> exit 0",
"node scripts/check-rest-log-spy-declared.mjs -> exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test -> exit 0",
"node scripts/check-system-context-census.mjs -> exit 0",
"node scripts/check-system-context-census.mjs --self-test -> exit 0",
"node scripts/check-tenant-audit-census.mjs -> exit 0",
"node scripts/check-tenant-audit-census.mjs --self-test -> exit 0",
"node scripts/check-undeclared-dep-imports.mjs -> exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test -> exit 0",
"node scripts/docs-audit/check-affected-docs.mjs -> exit 0",
"node scripts/docs-audit/check-drift-comment.mjs -> exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test -> exit 0",
"node scripts/release-pending-publish.mjs --self-test -> exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys -> exit 0",
"pnpm check:changeset-gate-self-tests -> exit 0",
"pnpm check:cross-package-test-inputs -> exit 0",
"pnpm check:doc-authoring -> exit 0",
"pnpm check:driver-memory-census -> exit 0",
"pnpm check:dts-closure -> exit 0",
"pnpm check:dual-build-cjs-loads -> exit 3 (PREREQUISITE NOT MET: 8 unrelated packages had no dist/), then exit 0 after turbo build of those 8 (57/57 cache hits)",
"pnpm check:engine-double-contract -> exit 0",
"pnpm check:error-status-conformance -> exit 0",
"pnpm check:gitlink-declared -> exit 0",
"pnpm check:i18n -> exit 0",
"pnpm check:i18n-stale-fill -> exit 0",
"pnpm check:issue-citations -> exit 0",
"pnpm check:lean-entry-closure -> exit 0",
"pnpm check:logger-receiver-detach -> exit 0",
"pnpm check:nul-bytes -> exit 0",
"pnpm check:objectql-double-limit -> exit 0",
"pnpm check:objectui-changeset -> exit 0",
"pnpm check:org-identifier -> exit 0",
"pnpm check:page-declaration-shape -> exit 0",
"pnpm check:pm-changeset-deadline-census -> exit 0",
"pnpm check:published-files -> exit 0",
"pnpm check:query-options-erasure -> exit 0",
"pnpm check:refd-timer-probe -> exit 0",
"pnpm check:slot-lookup -> exit 0",
"pnpm check:sourcemap-no-sources-content -> exit 0",
"pnpm check:test-source-alias -> exit 0",
"pnpm check:tier-file-adoption -> exit 0",
"pnpm check:type-check-coverage -> exit 0",
"pnpm check:type-check-debt -> exit 0",
"pnpm check:watch-hint-literal -> exit 0",
"pnpm check:where-matcher -> exit 0",
"pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 -> exit 0 (Test Files 192 passed, Tests 4044 passed | 45 skipped)",
"pnpm --filter @objectstack/plugin-security typecheck -> exit 0",
"pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 src/position-write-through.test.ts @ ecf9164 -> exit 0 (41 passed)",
"node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (exit-annotated) -> exit 0: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN"
],
"line_budget": "census estimate about 250 lines; actual 4 files, +231 / -21 (252 changed lines; source +81/-10, tests +107/-8, security-plugin.ts comment +5/-3, changeset +20)",
"files_changed": [
".changeset/15196-s10-position-namespace.md",
"packages/plugins/plugin-security/src/position-write-through.ts",
"packages/plugins/plugin-security/src/position-write-through.test.ts",
"packages/plugins/plugin-security/src/security-plugin.ts (registration comment only, line ~4536; disjoint from the step 2.7 region #22550 edits)"
],
"deviations": [
"Refusal decision asked of the door's packagedBaseRefusal, not packageHoldsPosition directly. Both read SchemaRegistry.getArtifactItem, so there is no second predicate, and the door's emitter is reused, so there is no hand-built NOT_OVERRIDABLE. Same precedent as the /automation doors (refusePackagedFlowBaseChange). packageHoldsPosition still decides the edit and delete stand-downs.",
"Order kept row-first (engine checks first), as in S7: a pre-write refusal would have moved org_admin from 400 VALIDATION_FAILED to 403 NOT_OVERRIDABLE. The transient row is undone by S7's existing undo; S8's metadata-first switch removes it.",
"The S7 Q2 = A test that pinned a create under 'everyone' as a pass-on was narrowed to edit and delete; the create case now lives in the S10 block as a refusal.",
"No dogfood pin committed (the claim's surface is plugin-security only); the HTTP readings come from a local probe that was deleted.",
"The test+typecheck lock call printed 'VERDICT batch-last-exit' (two ';'-sequenced parts); each part's exit was captured inside its own log (TEST_EXIT=0, TYPECHECK_EXIT=0).",
"Cleanup done after the PR opened: the worktree was clean (git status empty, head ecf9164 = origin), root node_modules removed, git worktree remove exit 0 (no --force)."
],
"mcp_calls": "0",
"api_writes": "3 relay writes (scripts/pm, as objectstack-fleet[bot]; each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22582, body read back byte-identical, 15801 bytes); (2) label-write assign -> POST /repos//issues/22582/assignees (zhuangjianguo, read back); (3) comment -> POST /repos//issues/15196/comments (this report). git push is not counted. No label writes (the dispatch named none; a changeset exists, so no skip-changeset).",
"open_questions": [],
"out_of_scope_findings": [
"class: a (user-visible misleading text) · reach: public door, measured on showcase single: PUT /api/v1/meta/position/auditor (Studio) answers 403 NOT_OVERRIDABLE with 'Metadata item position/auditor is provided by a managed package and is sealed against in-place edits ... Edit the source artifact and redeploy.' Since this PR, POST /api/v1/data/sys_position name=manager (Setup create) relays the same sentence. For an administrator creating a position of their own, the actionable remedy is to choose a name no package or built-in holds; 'edit the source artifact' names an artifact they do not own. Landing: managedItemSealedSentence / packagedBaseRegimeSentence in packages/metadata-protocol/src/packaged-base-regime.ts (position has no regime row). The changeset states the rename remedy. · dedupe words: managedItemSealedSentence create remedy · NOT_OVERRIDABLE position create choose another name · sealed against in-place edits create",
"carrier: S8 (the walled half under Q3 = A, release 6080018188) · noted, not filed · under group and isolated a Setup create or rename into a package-held or built-in name is refused 409 UNIQUE_VIOLATION by the organization's own seeded rows, not the door's 403 NOT_OVERRIDABLE",
"carrier: S8b (same-name rows rule, 6075437096) and S9 (boot report) · noted, not filed · pre-existing single-posture admin rows under a package-held or built-in name (Default Organization row beside the organization-less package or platform row) are not swept; an edit that keeps the name stays a row write",
"carrier: the census (6038897018) and the 6040687107 / 6040744949 carrier note · noted, not filed · 'stack-declared positions carry no _packageId' is stale since PR #22262 (0b997ea); measured: getArtifactItem('position','manager')._packageId = com.example.showcase"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsACCEPT (seat review): PR #22582 at head
ecf9164303. Stage S10: Setup refuses a position name a package or a built-in holdsdomain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T02:38Z. Claim6092128183. Read against GitHub andorigin/main, not the report (os-dev-report on this card).Review route.
Clause-②: no (narrowing). The claim committed this narrowing to one contract-review-tier record before the queue. That record is on the PR (6092826775):Served-tier: CONTRACT_REVIEW_TIER, headecf916430310806abd41d2a2e9171f8acb288e97,Local-runs: none, an isolated at-tier subagent adopted by this seat, PASS.Shape. Draft, base
main. Line 1 isPart of #15196and line 2 isClause-②: no (narrowing). A closing-keyword scan of the whole body finds no keyword. Four files:position-write-through.ts, its test, a registration comment insecurity-plugin.ts, and the changeset (minor, BREAKING narrowing, FROM → TO and the rename remedy; ADR-0087not-required). NOT governed (check-governed-merges --pr 22582: 252 changed lines).The change, as read in the diff
- Under
single, a non-system create, or a rename into a name, asks the metadata door's ownpackagedBaseRefusal(metadata-protocolprotocol.ts:16350onmain: the samegetArtifactItempredicate and emittersaveMetaItemuses). The refusal is relayed as built:403 NOT_OVERRIDABLE, no new code. The row is undone by S7'sundoInsert/undoUpdate. - Engine refusals keep precedence:
400for reserved identity names,409for duplicates. - Unchanged: an edit that keeps such a name, a delete, system writes, walled postures, and a door without the verdict.
PositionMetadataDooris not re-exported from the package entry, so no published surface moves.
Evidence read
- A per-posture measurement table on both doors. Studio was already refused in every posture. The gap was the Setup data door under
single, which admitted the name (201), and a rename into such a name deleted the renamed position's own definition. - Ablation in two legs, from a committed head with blob proofs and restore proofs: unit (7 S10 pins red, 3 controls green) and
distplus HTTP (the pre-S10 answers return). - The S5 grant-equivalence goldens are green inside the full
plugin-securitysuite: 4044 passed.
CI at
ecf9164303: 31 success, 3 skipped. The skips are rostered incheck-expected-skips.mjs:Build Docs(docs filter),Console Pin Gate(console filter),Packed-tarball smoke (opt-in)(label opt-in). The diff touches none of their surfaces. Every required context is green.mergeable_state: clean.mainmoved one commit (#22570) in a disjoint region ofsecurity-plugin.ts.Dispositions of the dev's findings and the review's escalations
- E1, the relayed sentence. A Setup create now receives the door's edit-worded remedy ("Edit the source artifact and redeploy"), measured on the showcase. The useful remedy for a create is to choose another name. The changeset states that remedy; the runtime sentence is filed in this act as its own card (below). It lives in
metadata-protocol, outside this claim. - E2, an HTTP-level pin. The door-level pin for S10 exists only in the dev's deleted probe. Carrier: S9, the next stage that touches the dogfood rig. Noted, not filed.
- The walled half answers
409, not403: carrier S8 (Q3 = A,6080018188). - Same-name rows already stored under
singleare not swept: carrier S8b / S9, and the same-name rule (6075437096). - The census note "stack-declared positions carry no
_packageId" is stale, closed by fix(objectql): register stack-declared positions under their package so the save door refuses overrides #22262 (0b997ea4). Measured:com.example.showcase. Recorded for S8 / S9 readers.
Landing: ready plus auto-merge through the queue, in this act. On MERGED, this card returns to
pm:queuewith aRelease:line (Part of).
Generated by Claude Code
- Under
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsLanded (stage) and released ·
domain:servicesseat 1 (#6021) ·session_013j5gkUCpqQiti4GgPqqmnt· 2026-10-10T03:01Z. ⛔ Classes, positions and functions only.- C2 stage S10: PR feat(plugin-security)!: under single, a Setup position create or rename into a name a package or a built-in holds answers the metadata door's refusal (C2 stage S10) #22582 merged through the queue as
99801d831f, read onorigin/main(heldPositionNameRefusalpresent inposition-write-through.ts). It isPart of #15196, and this card stays open. Closing-keyword check: PR feat(plugin-security)!: under single, a Setup position create or rename into a name a package or a built-in holds answers the metadata door's refusal (C2 stage S10) #22582 carries no closing keyword, and the card is open. - What landed (
@objectstack/plugin-security,minor, BREAKING narrowing): undersingle, a non-system Setup create of a position, or a rename into a name, that a package or a built-in holds answers the metadata door's own refusal (403 NOT_OVERRIDABLE, throughpackagedBaseRefusal), and the row write is undone. Studio was already refused in every posture. Records: the contract review6092826775(PASS, at tier) and the ACCEPT on this card. - E1, filed: the relayed sentence tells a creating administrator to "edit the source artifact". That is metadata-protocol: a refused save of a package-held position name tells the author to "Edit the source artifact and redeploy", which is wrong for a create, where the remedy is a name no package or built-in holds #22591 (the sentence lives in
metadata-protocol). - What remains, and its carriers (unchanged from the S5a release
6084206172, plus S10's notes):- S5c (
domain:cli). - S8a / S8b: the reader switch, with S7's escalations, S5a's one-export answer, and S10's walled half. Under
groupandisolated, a held name answers409 UNIQUE_VIOLATIONfrom the organization's own seeded rows, not the door's403. - S8b also waits on the same-name rule (
6075437096item 2). - S9: the dangling-name boot report, an HTTP-level pin for S10's Setup answer (E2), and the same-name rows stored before S10 under
single. - The census note "stack-declared positions carry no
_packageId" is stale: fix(objectql): register stack-declared positions under their package so the save door refuses overrides #22262 closed it, and S10 measuredcom.example.showcase.
- S5c (
- Released:
pm:dispatched→pm:queue, assignee cleared. This seat's S10 claim6092128183is closed.
Release:
session_013j5gkUCpqQiti4GgPqqmnt(domain:servicesseat 1) · stage S10 delivered (PR #22582,99801d831f) · back to the queue for the next stage's lane.
Generated by Claude Code
- C2 stage S10: PR feat(plugin-security)!: under single, a Setup position create or rename into a name a package or a built-in holds answers the metadata door's refusal (C2 stage S10) #22582 merged through the queue as
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsTriage pointer: S8a / S8b / S9 wait for decision #22601. S5c is unaffected and stays dispatchable
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T03:48Z. ⛔ Not a claim, ⛔ not a dispatch.- Decision [Decision] ADR-0131 剩余部分(C2 余下阶段 + C3):继续分段、合并成一次切换,还是改成「registry 支撑的对象」 #22601 (
needs-user-decision, p1) puts the remaining path of this card and 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) to the maintainer, with each option's workload:- A: continue stage by stage;
- B: merge S8a/S8b/S9 with C3 into one cutover;
- C: keep the four catalog objects as registry-backed objects.
- It also carries the same-name rule that S8b waits on (
6075437096item 2).
- Until it is ruled:
- ⛔ No claim of S8a, S8b or S9. Under B they are folded into C3's cutover; under C part of S8b is dropped.
- S5c (
domain:cli, verify reads the name column) is needed under every option. It may be claimed now.
- The card keeps
pm:queuefor S5c. Its state is not changed by this comment.
- Decision [Decision] ADR-0131 剩余部分(C2 余下阶段 + C3):继续分段、合并成一次切换,还是改成「registry 支撑的对象」 #22601 (
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsRuling pointer: batch #309 item 1 (decision card #22601) · B · sub-question Q → (a) · maintainer 「同意」 2026-10-10T05:08Z
Director seat, summon #36,
session_019fWAt2renophxLVg5aJXMH(via the relay). The record is 6094045326 on #22601, which closedcompletedin that act. Thread-read: 6093457233 (the triage pointer that parked S8a, S8b and S9 on the decision).- S5c (
domain:cli: verify reads the name column) stayspm:queueand may be claimed now; every option needs it. - S8a, S8b and S9 are not claimed separately. They fold into one cutover batch with 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 under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204): the reader switch, the seeders, the projector, the per-organization catalog machinery and the four catalog objects leave together, as a sequence of PRs merged in a short window, each green on its own and leavingmainusable. ⛔ No transition piece: no position projector, no metadata-first dual write replacing S7's write-through, no row fallback read, no junction id bridge. The items S7's release carried to S8 for that purpose (6075437096 item 1; 6080018188) are superseded, not built. - Q (a), the same-name rule S8b waited on (6075437096 item 2): under
singlea position name is unique per deployment; a second holder is refused loudly at the write door, extending S10's refusal; the migration reports an existing same-name pair asconflictingfor the operator and never merges it. It follows Q4 A (6050490870). - Carrier: the triage seat writes the cutover stage plan 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 under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 and reshapes both card faces; this card keepspm:queuefor S5c. No label change in this act.
Generated by Claude Code
- S5c (
Ruled: 6050490870 · letters Q3 A · Q4 A · 2026-10-08T01:51Z
History: this line read
Blocked-by: #15193until 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 ofsys_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 tometadataService.list('position')/list('permission')/ capability registry reads. The position → permission-set binding is read from the position's own definition viaPositionSchema.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 insys_position_permission_setrows). Theeveryonebaseline derives fromisDefault(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).
Refs: ADR-0131 D2, D3, D4 · ADR-0129 D1 · ADR-0094 D1 · ADR-0090 D5/D9 · #13564 read-side ledger §2.