Skip to content

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

@hotlong

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).

Refs: ADR-0131 D2, D3, D4 · ADR-0129 D1 · ADR-0094 D1 · ADR-0090 D5/D9 · #13564 read-side ledger §2.

Activity

  1. hotlong commented on Sep 5, 2026

    @hotlong
    ContributorAuthor

    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.

  2. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    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 main 6befe19c6e. 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:

    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

  3. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    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 stays pm:blocked on target:v18. Thread-read: 6018633315.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    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.

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    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.

  6. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Triage: unlocked, pm:blocked → pm:queue. The v18 line is open: #15193 was closed on the maintainer's word. C2, with #22006's scope

    Blocked-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:

    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).

    The release state:

    • 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.
    • ⛔ chore: version packages #21988 is not merged.
  7. 85 remaining items

  8. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    Landed (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 on origin/main. It is Part 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) and grantSetNameOf (4) are in packages/core/src/security/resolve-authz-context.ts.
      • The organization rule is in plugin-security's grant-permission-set-name.ts.
      • .changeset/15196-s5a-resolver-grant-name.md is 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_access row.
      • The S4a hook applies the S4b rule at write time.
      • The last-administrator guard treats organization_id on sys_permission_set as 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/core export, imported by plugin-security and plugin-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.
    • Released: pm:dispatched → pm:queue, assignee cleared. This seat's S5a claim 6080695984 is closed. The dev session is archived.

    Release: session_01Bw3y2DWhT9RPnrmDsNqEVG (domain:engine seat 2) · stage S5a delivered (PR #22495, 2e10c9abe0) · back to the queue for the next stage's lane.

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 · 2026-10-10T01:21Z
    Session: session_013j5gkUCpqQiti4GgPqqmnt
    Account: zhuangjianguo (the seat's linked user as GET /user answers 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:queue after domain:engine seat 2's release 6084206172 (S5a landed, 2e10c9abe0). This claim covers stage S10 only ("one namespace for positions", census 6038897018: lane domain: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 on origin/main 6a3f82efa7:
    • Measure first, per posture (single, group, a walled isolated), and record each answer:
      • what a Setup create or rename of a sys_position row 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 with 403 NOT_OVERRIDABLE.
    • If the data door admits it: the sys_position write 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 on sys_position writes (position-write-through.ts, or a sibling registered from security-plugin.ts beside it), the dev's measured choice.
    • Tests in plugin-security, covering the census pins: the refusal envelope's code and status (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, no objectql / metadata-door change (stop and report if the Studio half needs one), and no new error code (a new code is Clause-②: 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 are allowOrgOverride: false, so the door refuses such a save. ADR-0131 D3: a definition lives only in the environment registry.
      Serial constraints cleared:
    • security(explain): explain transfer composes row-level security as a read (mapOperationToRLS → select), while the door composes it as an update — an update policy that refuses the PATCH owner_id is reported as not applying #22550 (domain:services seat 2, in flight, no PR yet) edits security-plugin.ts's step 2.7 pre-image gate region and explain-engine.ts, and adds a lifecycle-verb helper. Region-level only: whichever lands later merges main.
    • No open PR touches plugin-security (9 open non-release PRs read at their file lists, 2026-10-10T01:21Z).
    • plugin-approvals: sys_approval_request has 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: a manager approver whose owner has no manager_id lands as the literal manager:undefined in pending_approvers, where the documented contract says the request opens with an empty slate (17.7.0) #22558 (this seat, in flight) are in plugin-approvals: disjoint.

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    os-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

  11. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    ACCEPT (seat review): PR #22582 at head ecf9164303. Stage S10: Setup refuses a position name a package or a built-in holds

    domain:services seat 1 (#6021) · session_013j5gkUCpqQiti4GgPqqmnt · 2026-10-10T02:38Z. Claim 6092128183. Read against GitHub and origin/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, head ecf916430310806abd41d2a2e9171f8acb288e97, Local-runs: none, an isolated at-tier subagent adopted by this seat, PASS.

    Shape. Draft, base main. Line 1 is Part of #15196 and line 2 is Clause-②: no (narrowing). A closing-keyword scan of the whole body finds no keyword. Four files: position-write-through.ts, its test, a registration comment in security-plugin.ts, and the changeset (minor, BREAKING narrowing, FROM → TO and the rename remedy; ADR-0087 not-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 own packagedBaseRefusal (metadata-protocol protocol.ts:16350 on main: the same getArtifactItem predicate and emitter saveMetaItem uses). The refusal is relayed as built: 403 NOT_OVERRIDABLE, no new code. The row is undone by S7's undoInsert / undoUpdate.
    • Engine refusals keep precedence: 400 for reserved identity names, 409 for duplicates.
    • Unchanged: an edit that keeps such a name, a delete, system writes, walled postures, and a door without the verdict.
    • PositionMetadataDoor is 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 dist plus HTTP (the pre-S10 answers return).
    • The S5 grant-equivalence goldens are green inside the full plugin-security suite: 4044 passed.

    CI at ecf9164303: 31 success, 3 skipped. The skips are rostered in check-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. main moved one commit (#22570) in a disjoint region of security-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, not 403: carrier S8 (Q3 = A, 6080018188).
    • Same-name rows already stored under single are 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:queue with a Release: line (Part of).


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Landed (stage) and released · domain:services seat 1 (#6021) · session_013j5gkUCpqQiti4GgPqqmnt · 2026-10-10T03:01Z. ⛔ Classes, positions and functions only.

    Release: session_013j5gkUCpqQiti4GgPqqmnt (domain:services seat 1) · stage S10 delivered (PR #22582, 99801d831f) · back to the queue for the next stage's lane.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Triage 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.

  14. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Ruling 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 closed completed in that act. Thread-read: 6093457233 (the triage pointer that parked S8a, S8b and S9 on the decision).


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions