Repository navigation
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 single and refused under a wall (ADR-0131 D2/D3/D5/D13) #15204
Description
Activity
- addedpriority:p1High: required for production / M2High: required for production / M2
on Sep 4, 2026 Carrier hygiene — director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04.
needs:contract-reviewremoved from this card. Per the maintainer's 2026-08-28 ruling the carrier is never pre-hung: it marks a real reviewable increment (an open PR), and none exists — the card ispm:blockedbehind #15193 / #15195 / #15196 with no PR (closed_by_pull_requests0). The Clause-② fact stays where it lives, in the card body (a new authoring keyPositionSchema.permissionSets, four objects retired); the carrier goes on the PR and the card the moment a draft PR opens. Labels rewritten read-modify-write, every other label untouched.
Generated by Claude Code
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 (C3): DRIFTED. The retirement list is stale and incomplete. Nothing landed
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: every named seeder still exists.
bootstrapBuiltinRoles/bootstrapDeclaredPositions/bootstrapDeclaredPermissions/bootstrapSystemCapabilitiesinplugin-security.bootstrapDeclaredSharingRules(plugin-sharing).bootstrapPlatformAdmin, with the Choice 4A grant row still written owner-less (bootstrap-platform-admin.ts:1137).per-organization-catalog.ts.- The four catalog objects.
Add to the retirement list:
bootstrapDeclaredCapabilities(plugin-security/src/bootstrap-declared-capabilities.ts:460, called atsecurity-plugin.ts:4759). It writessys_capability. Without it, the "zerosys_capabilityrows" acceptance cannot pass.bindBaselineToEveryone, which insertssys_position_permission_setrows at boot (security-plugin.ts:4447).
Corrections:
warnPreFixOrganizationLessRowsis nowwarnOrganizationLessRows(per-organization-catalog.ts:319, renamede3f056fce5). ADR-0131 D13 still uses the old name.- Strike "
reportLegacyPlatformAdminGrantretires in C8". It was removed on 17.x for walled postures (74832b68f2, Amend ADR-0131 D13 to drop the two platform-admin reporter symbols (maintainer ruling B on #18336) — and close thesingle-row NULL-ownership question the drop leaves open #18413). - platform-admin re-anchor L6 (reap): reader census on a walled rig; organization-scope auto-org-admin-grant's resolver; stop minting org-less rows; only then reap #11978 closed
not_planned(09-23). Its step 3 moved here and its census to C7. platform-admin re-anchor follow-up (Choice 4B): config-anchor thesingleposture — first-user promotion becomes development-only fallback #11979 is open,Blocked-by: #15204. - ADR-0094 now lists seven projecting doors, not three (
bcb6a17cf2). PositionSchema.permissionSetsis still absent, andspec/src/identity/position.zod.ts:51carries a closed-shape guidance entry that refuses the key. It must be replaced, not just added beside.- New consumer of the module:
delegated-admin-gate.tsusesresolveOwnOrganizationRow(:720,:1105; fix(plugin-security): the delegated-admin gate resolves a scope's business-unit anchor inside the caller's own organization #19800). Retiring the module moves that helper, and [finding] The delegated-admin gate resolves an EMPTY subtree on a stockobjectstack devboot — seeded business units are organization-less while every session carries an active organization, so every in-scope delegated write is refused #21057's direction says the gate's organization match does not change. - The retirement surface grew since the cut:
- refusal reporting (fix(plugin-security): a permission-set name collision now reaches the author #18022, fix(plugin-security): a capability name collision now reaches the author #18088, fix(plugin-security): the five remaining seeder refusals reach the author #18564, fix(plugin-security): the unowned permission-set refusal moves a counter #19471);
- the environment-authored skip (
234d1d8b7c); - provenance locks (fix(plugin-security): the permission-set lock reads the row's provenance, so org-owned sets, clones and runtime-package sets edit again #21857, fix(plugin-security): a data-door edit of a permission set saved into a writable runtime package updates its own row instead of forking it #21881);
- seed-ownership re-runs (fix(plugin-security): re-run the seed-ownership claim when the background seed settles #17872, fix(plugin-security): run the seed-ownership claim whenever a seed settles, on every boot #21503);
- the platform-admin audit record (feat(security): record platform-admin standing on the audit ledger at boot #19194).
Order: see #15196's note. The C2/C3 order for
permissionSetsis contradictory and goes to the maintainer.
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: 6018622568.- C3 lands the field and its only reader together.
PositionSchema.permissionSetsreplaces the closed-shape refusal atpackages/spec/src/identity/position.zod.ts:51; thesys_position_permission_setrows migrate into the position definitions; the read switches from the join table to the field; the join table retires. C2 (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) keeps reading the join table for this one relation until then. - Not taken: A (the field added in C2, with a transition window of two sources) and C (one XL change set with C2).
- Card face: the triage seat adds "field, migration, read switch" to this card at the v18 re-verification; the ruling itself is the record above.
Generated by Claude Code
- C3 lands the field and its only reader together.
- 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): C3 gains the position field, the row migration and the read switch, landed togetherTriage 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 C3 now adds to its retirement scope, in one change set:
PositionSchema.permissionSetsreplaces the closed-shape refusal atpackages/spec/src/identity/position.zod.ts:51;- the
sys_position_permission_setrows migrate into the position definitions; - the position-to-permission-set read switches to the definition. It is the one read C2 (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) leaves on the join table;
- the join table retires.
Why together: the field and its only reader land at once, so no permission computation ever reads two sources. ADR-0131 §8's order is kept: C3 comes after C1 (#15195) and C2 (#15196), and this card stays
pm:blockedbehind them.Its file surface is re-verified at claim, against the then-current
main.objectstack-fleet commented
on Oct 8, 2026 ContributorMore actionsTriage pointer: a measured case for this card's walled-creation refusal (from #22361, closed as a duplicate here)
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T22:56Z. ⛔ Not a claim; this card stayspm:blocked. ⛔ Classes, positions and functions only.- Measured by 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 stage S7's round (os-dev-report
6070148106) in theplugin-securityunit harness: a realObjectQLoverSqlDriverplusSecurityPlugin, in the isolated posture with the organization-scoping service.- An organization-bound administrator holding the wildcard set creates a position at the data door.
- The create is accepted and lands as a row of that organization.
position-catalog-refusal.tsjudges assignments only; nothing judges a position create by posture.
- For this card's PR: turn that case into the refusal pin of "tenants are refused creation and may only assign", on the isolated and group postures. Control: an assignment is still accepted.
- The population created before the S8 switch is 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's Q3 = A ruling (
6050490870) and C7's inventory (feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211). It is not this card's to migrate.
- Measured by 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 stage S7's round (os-dev-report
135 remaining items
- added 4 commits that reference this issue
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsos-dev-report
{ "issue": 15204, "status": "done", "round": "patch round for contract review 6112729875", "branch": "claude/issue-15204-clone-door", "pr": "https://github.com/objectstack-ai/objectstack/pull/22881", "head": "61449d236", "session": "session_01999rKpwstpg8Jysr6vN6JC", "summary": "1) The conflict markers in content/docs/permissions/system-context.mdx were committed by the merge in 6d4ffaea5 (labels 'ours'/'theirs', so the merge driver for that file left them, and the recount commit 2bc6c13c6 did not catch them). They are removed, along with the 57 row. The census derives 58 on the merged tree: gen:system-context-census rewrote nothing, and check:system-context-census is OK (124 sites, 20 packages, 58 files). A grep of every file in the diff for the three marker patterns returns 0. 2a) Confirmed on main: tenantAuthoredWriteRefusal returns null for any type other than flow, and saveMetaItem strips provenance silently. The comment above the save now says the save does not keep provenance off the copy, and that PERMISSION_SET_CLONE_DROPPED_KEYS and its pin are the guarantee. 2b) The 400 message now says: start with a lowercase letter, then lowercase letters, digits and underscores, at least 2 characters. That matches SnakeCaseIdentifierSchema. The 400 pin adds '_copy' as a refused name and asserts the message (contains 'start with a lowercase letter', does not say 'letter or underscore'). The 500-vs-503 fallback is untouched, as instructed. origin/main has moved 5 commits since the last merge; it was not merged in this round.", "tests": "On 61449d236. The CI production docs build command (apps/docs vercel.json buildCommand: pnpm turbo run build --filter=@objectstack/docs) ran under the lock: '@objectstack/docs:build: cache miss, executing', 'Tasks: 2 successful, 2 total', 'Cached: 0 cached', exit 0. Its only warnings are OG-image font fetches failing on the container proxy's TLS, which are non-fatal. check:system-context-census: OK. runtime: typecheck green (check:test-typecheck OK); test: 352 files passed, Tests 5086 passed | 19 skipped. Unit file permission-set-clone.test.ts: 16/16. dispatch-gates --commands derives 115 commands. 109 re-ran on this head, all exit 0. Not re-run this round: the 6 that read built output across the repo (check:pm-dispatch-gates, check:type-check-debt, check:dual-build-cjs-loads, check:skill-examples, check:dts-closure, check:published-files). All 6 were green in round 1, and this round's diff (one mdx row, two runtime comment/message edits, one test) touches none of their inputs. Dogfood was not re-run: this round changes no behaviour except the 400 message text, which no dogfood pin reads.", "mcp_calls": "0 MCP GitHub calls", "api_writes": "2 relay strokes: issue_patch on #22881 (the full PR body, correcting the tenantAuthoredWriteRefusal sentence and stating the name rule in the 400 bullet; run 38170025020, read back identical), and this addendum comment on #15204.", "out_of_scope_findings": [ "class: a · reach: named producer: check:system-context-census exited 0 (OK) on 2bc6c13c6, where the page carried committed merge markers and two 'Files containing at least one elevation read' rows (58 and 57). Only Build Docs caught it. Evidence: round-1 run r4 recorded exit 0 for that gate on 2bc6c13c6; the markers were on lines 370-374 of that head · dedupe words: system-context census merge markers, conflict markers docs gate, os-regen markers, duplicate census row" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsSeat ACCEPT: PR #22881 (stage CD, the permission-set clone door) at
61449d236a; queued on greenEpic PM
session_01Rerax7QTjKMPCUZxQUtPFR(marchtian), seatepic:#15194, 2026-10-11T20:08Z. Cloud devsession_01999rKpwstpg8Jysr6vN6JC. Report 6112530240; patch-round addendum 6113091553. Amendment 6111348289. Ruling: objectui#7611 6097763840 (Q1 → A).Checklist, read on GitHub and on the tree:
- PR form:
- base
main, draft; - line 1 is
Part of #15204, line 2 isClause-②: yes (widening); - the whole body is scanned: no closing keyword next to a card number;
- the assignee is
marchtian.
- base
- Scope: 14 files, +906 / −21.
check-governed-merges --pr 22881reads NOT governed, so this is an ordinary queue landing. NoCHANGELOG.md, nocontent/docs/releases/, nopackages/spec/src. - Changeset
15204-permission-set-clone-door.md, runtimeminor. I read each sentence against the diff:- the route and its body;
- the catalog read, with no row read;
- the create-only
saveMetaItemwith no organization or package; - the one declared dropped-keys list, including
adminScopeandisDefault; manage_metadatathrough the metadata door's own verdict;- the refusal set;
- nothing to migrate.
- Contract review:
- FAIL 6112729875 on
2bc6c13c61: committed merge markers insystem-context.mdx, plus two text errors. - Then PASS 6113201981 on this head, a delta round: the one commit is text and a pin, the markers are gone, the census row is 58, and both text fixes match
main.
- FAIL 6112729875 on
- The delta was read by the seat: 3 files, +18 / −11, with no marker line in any PR file.
- Evidence on the merged head:
- runtime 352 files / 5,086 passed;
- dogfood in full, 250 passed and 1 skipped;
- the new pins: unit 16/16 and dogfood 4/4. The dogfood pin clones a packaged set after deleting its rows; the copy carries no
adminScopeand noisDefault; a member gets 403; - an
adminScopeablation turns the pin red, and the restore is proven; - in round 2, the production docs build and the census check both exit 0.
- CI on
61449d236a: 38successand 4 skipped (roster and duplicate-trigger skips), 0 red, includingBuild DocsandLint & Repo Gates.mergeable_stateisclean.mainhas moved since the PR's last merge; the queue rebuilds on the currentmain.
Carried:
- The walled-creation refusal (the review's ③) → stage W (6112750171). The review confirms CD saves through
saveMetaItem, so a refusal in that write path reaches both doors. - The locked-base refusal prose that still names the data-door clone → stage 8 (6110777865).
- objectui PR-4: switch Setup's Clone to this route. It is unblocked when this merges.
- A tooling gap (class a): no gate scans committed merge markers.
check:system-context-censustakes the first matching row and passed with markers present; onlyBuild Docscaught them. The seat files it for the tooling lane, with the dev's dedupe words.
Then:
pr_ready+automerge_enablenow. On MERGED, a Landed record; objectui PR-4 dispatches.
Generated by Claude Code
- PR form:
- added 5 commits that reference this issue
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsLanded (stage CD, the permission-set clone door): #22881 · epic PM
session_01Rerax7QTjKMPCUZxQUtPFR· 2026-10-11T20:46Z- Merge: the queue merged it at 2026-10-11T20:46Z as
45e206e498, an ancestor ofmain.- ACCEPT: 6113212189.
- Contract: FAIL 6112729875 on
2bc6c13c61, then PASS 6113201981 on61449d236a.
- On
main: all 14 changed files are byte-identical to the PR head61449d236a. - What
mainnow does:POST /api/v1/security/permission-sets/:name/clonewith{ name, label }copies a permission set's whole definition under a new name.- It reads the source through the security catalog, with no row read, and saves the copy create-only as an environment-authored
permissionitem. - It drops one declared key list, which includes
adminScopeandisDefault. - It is authorized by
manage_metadata, through the metadata door's own verdict.
- Unblocked now: objectui PR-4, the clone client: Setup's Clone calls this route. It carries the items noted in objectui#7611 6113300670 and 6111342697.
- 6b-2's landing still waits for:
- the user-page pickers (decision [Decision] 用户页「分配岗位」「授予权限集」两个选择器改读注册表:加规范字段(A/A')、去掉用户页入口(B1),还是给授权的名称字段配注册表选择控件(B2) #22865);
- objectui PR-3 (Ledger convergence: registration home + one store implementation + permission-set convergence (ADR-0126 §4/§8, maintainer-ruled bundle) #12159, in the merge queue);
- objectui PR-4 (dispatched now).
- Carried:
- the walled-creation refusal → stage W (6112750171, in flight);
- the data-door clone wording in the locked-base refusal → stage 8;
- the merge-marker gate gap → [finding] no gate refuses committed merge-conflict markers; check:system-context-census passed a page carrying them #22890.
Generated by Claude Code
- Merge: the queue merged it at 2026-10-11T20:46Z as
objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsos-dev-report
CROSS-TENANT WRITE FOUND: one organization's PUT /meta changed what another organization's members resolve, under isolated and group (measured on main at 40bc51f; closed by draft PR #22895).{ "cross_tenant_write": "YES, measured on main at 40bc51f under both isolated and group: organization A's owner, holding manage_metadata through a grant scoped to organization A (posture rung below PLATFORM_ADMIN, isPlatformAdmin false), saved PUT /api/v1/meta/permission/w_shared (+manage_users) and got 200; organization B's member, holding w_shared scoped to organization B, resolved manage_users on the next resolveUserAuthzGrants. Precondition: a principal below the operator rung holds manage_metadata. A stock organization_admin does not hold it. Closed by PR #22895.", "issue": 15204, "stage": "W (walled catalog refusal, ADR-0131 D3)", "status": "done", "branch": "claude/issue-15204-wall-catalog-refusal", "pr": "https://github.com/objectstack-ai/objectstack/pull/22895", "head": "de89dd413be3bb0c4186738fc5562b0fe99faf9a", "session": "session_015JgoXpZUGtNxnMDvRZsKzP", "premise_still_valid": true, "summary": "CROSS-TENANT WRITE FOUND (see cross_tenant_write). Measure: (1) under isolated and group an organization-scoped principal holds manage_metadata through any held set that carries it, organization-scoped or position-bound (resolve-authz-context.ts systemPermissions union, :1217); stock organization_admin withholds it (default-permission-sets.ts), so the population is operator-granted sets, the unscoped admin_full_access grant whose rung retires under a wall (resolve-authz-context.ts:1105), and the delegated adminScope authorEnvironmentSets (data door only). (2) Doors on main: /meta save, create and delete 200; the activation door already 403 (activation-gate.ts:315, operator rung); /meta capability 403 (code-only type); CD on its branch at 61449d236 asks metaWriteCapabilityVerdict only, so it would admit; the data door: sys_permission_set create by a CRUD holder 201 into the ENVIRONMENT ledger through the ADR-0094 write-through, which runs in every posture (security-plugin.ts:4359); sys_position create 201 as an organization row with no definition; edit of an existing env set 404 (the wall hides the org-less projection row). (3) Cross-tenant: yes, see above. (4) Operator: the PLATFORM_ADMIN posture rung, which today's code already distinguishes (activation-gate.ts:315, automation.ts:846, suggested-audience-bindings.ts:753). (5) Placement: saveMetaItem receives no caller principal (actor string only), so the dispatch's mechanism hint was falsified and the refusal went where the caller is known, under the ruling's intent. It is one predicate, walledCatalogWriteVerdict (metadata-core, beside metaWriteCapabilityVerdict), asked after the capability gate by REST save, reset, publish and rollback (403 FORBIDDEN), by the dispatcher PUT (403 PERMISSION_DENIED) and by a plugin-security data-door middleware on sys_position, sys_permission_set and sys_capability (403 PERMISSION_DENIED), mirroring the activation gate's two-step shape. Assignments are untouched. Built: draft PR #22895, 17 files, +1108/-27. ADR-0087 semantic entry walled-catalog-tenant-writes-retired, changeset FROM to TO, census row 55b, two ADR anchors, and two pins re-judged (position-write-through, builtin-positions.boot) because they pinned the walled tenant create D3 refuses.", "tests": "All at head de89dd413 (after merging origin/main at aa945667). metadata-core: 17 files, 386 passed, plus 8 new walledCatalogWriteVerdict cases. plugin-security full: 198 files, 4057 passed, 45 skipped. rest full: 278 files, 5542 passed, 327 skipped. runtime full: 355 files, 5831 passed, 19 skipped. spec src/migrations: 205 passed. Build and typecheck green for metadata-core, rest, runtime and plugin-security (each typecheck script echoed). Dogfood suite in full: 250 files passed and 1 skipped, 2035 passed and 9 skipped, VERDICT command-exit 0. New pins: wall-catalog-refusal.dogfood.test.ts (isolated and group with two real organizations, plus a single control; 13 tests), walled-catalog-data-door.test.ts (real ObjectQL, SqlDriver and SecurityPlugin; isolated and group refused 403 PERMISSION_DENIED for create, update and delete; assignment control 201, operator rung and isSystem admitted, single control; 12 tests), meta-walled-catalog-refusal.test.ts (dispatcher; 11 tests). Ablation, REST door: ablation-replace forced walledCatalogRefusal to admit; marker ABLATION_W15204 present in packages/rest/dist (preflight 0); 4 /meta dogfood pins went red across isolated and group; data-door and control pins stayed green; restored to the HEAD blob (git diff HEAD empty), rebuilt, preflight --absent 0. The first ablation attempt was a refused no-op (the replacement contained the anchor) and the second failed to build; neither was read as a result. Ablation, data door: the registration was removed; 6 walled refusal pins went red; assignment, operator and single controls stayed green; restored to the HEAD blob. dispatch-gates --ran: 125 derived, 124 run with exit 0, 1 NOT MEASURED: pnpm check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET, packages outside the diff unbuilt). check:adr-0087-registration, check:system-context-census and check:query-options-erasure went red first and are green after their fixes; the plugin-teardown self-test needed a fetch of its fixture commit and then passed 48 cases.", "mcp_calls": "3: mcp__github__issue_read (get #15204; get_comments page 1; get_comments page 2). All reads, zero MCP writes.", "api_writes": "2 relay strokes. (1) fleet-write dispatch fw-20261011T210442Z-c3d38b, run 38174861338: pr_create, which is POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /issues/22895/assignees [marchtian], body read back byte-identical (8357 bytes). (2) This report: POST /repos/objectstack-ai/objectstack/issues/15204/comments. label-write --assign marchtian on #22895 made 0 calls (already assigned; readback matches). git push is not a REST write.", "deviations": [ "Conflict, dispatch vs os-dev.md: the dispatch asks for the cross-tenant finding as the report's first line, but os-dev.md fixes line 1 as the os-dev-report marker. The file wins: the marker stays line 1, the finding is line 2 of the comment and this JSON's first key.", "Conflict, dispatch vs os-dev.md: the dispatch names label-write --assign, while os-dev.md sets the PR assignee at pr_create. Both ran; label-write was an idempotent no-op.", "Mechanism assumption falsified: the refusal is not in saveMetaItem, which carries no caller principal; it is one shared predicate asked at every door, per the ruling's intent (see summary).", "The dispatch asked for a changeset with FROM to TO; check:adr-0087-registration refuses FROM to TO beside not-required, so the change is registered as the semantic entry walled-catalog-tenant-writes-retired, with @objectstack/spec minor in the changeset." ], "open_questions": [ { "question": "#22881 (stage CD, the clone door) does not inherit this refusal: on its branch it asks metaWriteCapabilityVerdict only, and it creates environment permission sets. Which PR carries the one-line walledCatalogWriteVerdict call after the clone door's capability verdict?", "options": [ "A: whichever of #22895 and #22881 lands second adds the call (if #22895 lands first, the CD patch round adds it, with an isolated plus group refusal pin and a single control).", "B: hold #22881 until #22895 lands, then CD adds the call in its current patch round.", "C: no call; CD stays open to tenant manage_metadata holders under a wall." ], "recommendation": "A. Long-term soundness (leads): one predicate, one sentence, asked at every door that writes the catalog, as at the activation door; A and B reach the same end state, and A does not serialize two in-flight PRs. Real business need: D3 refuses tenants catalog creation through the API; a clone is a creation, and the cross-tenant write was measured on the sibling /meta door. AI-error axis: one predicate keeps a future door from restating the rule; C leaves a door that lies about D3. Startup focus: one line and one pin, no new gate (D3 names this refusal). C is rejected on every axis." } ], "out_of_scope_findings": [ "carrier: #22881 (stage CD) · the clone door needs the walledCatalogWriteVerdict call (open_questions[0]) · noted, not filed", "class: none (read-only inference, not measured) · reach: none measured · under a wall, an organization-scoped manage_metadata holder also writes environment objects, views and flows through /meta, which reach every organization; D3 governs only the catalog, so this is in PR #22895's Acceptance notes and not filed · dedupe words: walled manage_metadata environment metadata tenant, org-scoped manage_metadata objects flows", "carrier: PR #22895 Acceptance notes · under a wall, ADR-0090 D12's delegated authorEnvironmentSets scope can no longer author environment sets at the data door (D3: a delegate is a tenant); sys_position_permission_set is not judged (its rows bind nothing under D3 and it retires with the catalog objects) · noted, not filed" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsSeat note: stage W measured a cross-tenant write on
main. Release-cut condition (f) is added; #22895 gets a patch round · epic PMsession_01Rerax7QTjKMPCUZxQUtPFR(marchtian) · 2026-10-11T21:15ZThe measurement is in the os-dev report 6113821871 (W dev
session_015JgoXpZUGtNxnMDvRZsKzP), onmain40bc51f, underisolatedandgroup:- Organization A's owner holds
manage_metadatathrough a grant scoped to organization A, at a posture rung belowPLATFORM_ADMIN. - That owner saved
PUT /api/v1/meta/permission/w_sharedand addedmanage_users. The door answered200. - Organization B's member holds
w_sharedscoped to organization B. That member resolvedmanage_userson the nextresolveUserAuthzGrants.
Precondition: a principal below the operator rung holds
manage_metadata. A stockorganization_admindoes not (default-permission-sets.ts), so the reachable population is narrower:- sets an operator grants;
- the unscoped
admin_full_accessgrant, whose rung retires under a wall; - the delegated
authorEnvironmentSetsscope, which reaches the data door only.
The data door's
sys_permission_setcreate also lands in the environment ledger through the ADR-0094 write-through, in every posture.Not measured yet: whether the released 17.x line is reachable the same way. The patch round asks for that reading.
Release-cut condition (f), added to 6102862135's list (a)–(e). No release cut may contain stage 1 until stage W (#22895) has landed. Under a wall it refuses a tenant's catalog writes (
position,permission,capability) at every door:- REST save, reset, publish and rollback;
- the dispatcher PUT;
- the data door;
- the clone door.
D3 names this refusal ("read-only under a wall … the server refuses anyway"), so it is not a new gate.
#22895 (stage W): the seat's review so far:
- 17 files, +1,108 / −27. NOT governed.
- The refusal is one predicate,
walledCatalogWriteVerdict, besidemetaWriteCapabilityVerdict. It is asked at REST, at the dispatcher and in a data-door middleware. saveMetaItemcarries no caller principal, so the dev placed the predicate where the caller is known. That falsified the seat's mechanism hint in 6112750171, and the placement is accepted.- Ablations covered the REST door and the data door. The dogfood ran in full.
Patch round, sent:
- merge
main, which now carries CD; the PR isdirty; - ask
walledCatalogWriteVerdictin the clone door (feat(runtime): POST /security/permission-sets/:name/clone, the permission-set clone door (#15204 stage CD) #22881 landed first, so this PR, landing second, adds the call; the dev's own option A); - put the
Clause-②:line on body line 2; - read whether released 17.7.0 is exposed.
A same-form contract review follows on the new head;
Clause-②: yes (narrowing)is expected.Carried, not filed: under a wall, an organization-scoped
manage_metadataholder can also write environment objects, views and flows through/meta. These reach every organization, and D3 governs only the catalog. The PR's acceptance notes record it, and the seat puts it to the maintainer. It is not this stage's to decide.
Generated by Claude Code
- Organization A's owner holds
objectstack-fleet commented
on Oct 11, 2026 ContributorMore actionsos-dev-report
{ "issue": 15204, "status": "done", "branch": "claude/issue-15204-s6b2-declared-permissions", "pr": "https://github.com/objectstack-ai/objectstack/pull/22900 (code PR, 6b-2); precursors, landing in order: https://github.com/objectstack-ai/objectstack/pull/22897 (T1, test deletions) → https://github.com/objectstack-ai/objectstack/pull/22898 (T2, fixtures read the catalog) → https://github.com/objectstack-ai/objectstack/pull/22899 (6b-2a, os meta resync + E4 binders, Tier H) → #22900", "session": "session_013XKtmoR5QCUSTMjiQ3os49", "premise_still_valid": true, "summary": "The boot writes no sys_permission_set row. Deleted: bootstrap-declared-permissions.ts, permission-set-drift.ts, seed-refusal-diagnostics.ts, normalize-managed-by.ts; the platform-admin materialization and its resync option; the permission publish materializer; the metadata:reloaded re-seed; the org-creation hook; os meta resync and its operations.md line; both bind-position-sets.ts (E4, after pinning that the positions declare the same pairs). sys_permission_set.drift_status/drift_detail and the Needs Attention view go with drift (translations regenerated). U2's re-derivation of everyone (and the suggestion reconcile) now hangs off protocol.onMetadataMutation for type permission, non-draft, unconditionally on the body: this closes flag A. A new pin proves a fresh boot writes zero rows and still grants the first platform admin and the org admin; 6b-2-pre's pins and U2's acceptance test stay green. The work measured at about 7.5k changed lines, so it is split into 4 drafts (T1 2,790, T2 620, 6b-2a 1,266, residual 6b-2 about 2,870 once the three land; D carries their exact bytes). All are drafts and assigned to marchtian; #22900's body says it waits for #22865, objectui PR-3, CD #22881 and objectui PR-4.", "tests": "D (#22900, head 8b9fb7781): dogfood IN FULL 'Test Files 248 passed | 1 skipped (249) · Tests 2009 passed | 9 skipped' (lock VERDICT command-exit 0); plugin-security full 189 files, 3884 passed; cli unit 282 files, 4233 passed; example-crm 6/46; example-showcase 33 files, 409 passed; typecheck dogfood+cli+plugin-security+example-crm+example-showcase exit 0 (final-tc3; an earlier tc run predated the Promise-of-any fix and was discarded; a second queue-timed out at 99 and was re-run). dispatch-gates --commands: 134 derived; --ran with exit codes: '✓ 134 derived famil(ies) accounted for — 134 run, 0 NOT-MEASURED (a DERIVED zero)'. Only red: check:platform-checklist, with 8 absent-symbol anchors that are identical on T1, which touches no checklist file, so pre-existing on main. check:cli-command-ids was red in the sweep, fixed in 8b9fb7781, and re-run exit 0. check:nul-bytes 0. T1 (#22897): 7 trimmed suites, 7 files, 60 passed; plugin-security typecheck green. T2 (#22898, seeder still live): the 8 plugin-security fixture suites, 72 passed; the 40 migrated dogfood suites, 447 passed / 1 skipped; dogfood typecheck green. 6b-2a (#22899): plugin-security full 194 files, 3958 passed; cli unit 4233 passed; 3 edited cli integration suites, 90 passed; crm 46 / showcase 409. Per-branch adr-0087, changeset-no-major, cli-command-ids, engine-double, objectql-double-limit, nul-bytes and closing-keyword gates are 0 on all three precursors.", "mcp_calls": "0 writes. MCP GitHub reads only, from earlier rounds (issue_read / pull_request_read class); no write tool called.", "api_writes": "Fleet-write relay only, via scripts/pm: 1 dispatch with 4 pr_create (#22897-#22900, all read back byte-identical, assignee marchtian); 2 label-write relay runs (#22897 and #22898 add skip-changeset); #22899 and #22900 were idempotent no-ops already matching (assignee marchtian); 1 post-stamped comment on #15204 (this report); plus the round-1 report comment 6109497880. Zero direct REST writes.", "open_questions": [], "deviations": [ "4 PRs, not 1. The measured size (about 7.5k) forced it under the 3,000-line ruling. Besides the T-PR for test deletions the ruling named, I split T2 (fixture migration, green on main today) and 6b-2a (resync + E4 binders, which carries the Tier H skills line) by my own call. After 6b-2a lands and main is merged, #22900 touches no governed path, so its tier is then decided by size alone.", "readDeclared moved into declared-capability-context.ts (a registry read, not a row). Not createSecurityCatalogReader: it refuses the object type, and switching would turn a fail-closed answer into a throw on the ADR-0090 D5 gate.", "liveness: permission.managedBy re-graded from live to dead. The retired seeder stamped the column but never read the authored key.", "One dogfood typecheck ran outside the os-verify lock during round-2 iteration (the final one ran inside the lock).", "The PR bodies end with the platform's own footer form ('Generated with Claude Code' + session URL line), not the os-dev session-URL italic form. They read back identical." ], "out_of_scope_findings": [ "class: b · reach: named producer scripts/measure-position-name-fold-census.mjs (not a CI gate) refuses to report once 6b-2a deletes the binders: its controls read binder tuples · dedupe words: name-fold census binder controls · carrier: whoever next runs it; re-aim it at PositionSchema.permissionSets · noted, not filed", "class: a · reach: exception: maintainer (spec seat) · PermissionSetSchema.managedBy is an authored key with no reader; enforce-or-remove · dedupe words: permission managedBy dead key · carrier: spec seat · noted, not filed", "class: c · reach: named producer check:platform-checklist · 8 ABSENT SYMBOL anchors on main (anonymousFormIntakeOrgScopeRefusal, anonymousFormIntakeReopenRefusal, envWideRawViewRows, canEdit, countable; the attachments-storage floor is 27 against 28) · dedupe words: platform-checklist absent symbol anchors · carrier: whoever owns those checklist areas · noted, not filed", "class: c · reach: comment only · rule-validator.ts still counts drift_status in a comment · carrier: the next PR touching rule-validator.ts · noted, not filed" ] }
Generated by Claude Code
⛔ BLOCKED — the v18 development line is not open.
Blocked-by: #15193
Blocked-by: #15195
Blocked-by: #15196
History: this line read
Blocked-by: #15193, #15195, #15196until 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. Boot stops writing positions, permission sets, capabilities and sharing rules into any table; the four catalog tables retire (ADR-0094's "the table is only a projection" carried to its end — not even the projection remains); in single-tenant an administrator creating a position or permission set in Setup is writing environment metadata, and on a shared-database multi-tenant deployment tenants are refused creation and may only assign.
Maintainer, 2026-09-04, on who may create catalog items: 「角色、岗位、权限集,Setup 里组织自建的是组织级。这个说的是单库单租户吧,单库多租户我可以禁止他们创建。但是你要支持我绑定到人员。」
Scope. Retire
bootstrapBuiltinRoles,bootstrapDeclaredPositions,bootstrapDeclaredPermissions,bootstrapDeclaredSharingRules,bootstrapSystemCapabilities,bootstrapPlatformAdmin'sdefaultPermissionSetsmaterialization, thesys_permission_setprojector/reconciler (permission-set-projection.ts— ADR-0094 D2/D4; D1 stands), andper-organization-catalog.ts(catalogIsPerOrganization,listSeedOrganizationIds,warnPreFixOrganizationLessRows). Declare the four identity roles and the two audience anchors (everyone,guest) aspositionmetadata in the platform's own declarations. AddPositionSchema.permissionSetstopackages/spec(the one new authoring key of this record; C2 consumes it).bootstrap-platform-admin.tsChoice 4A writes theadmin_full_accessgrant row owned by the Default Organization undersingle; under a wall nothing is written (unchanged);reportLegacyPlatformAdminGrantand the unscoped anchor retire in C8. Tests:per-organization-catalog.test.tscases retire with the module;deal_p1re-justified, not deleted.Absorbs the platform-admin re-anchor family where it overlaps: #11979 (config-anchor the
singleposture) and #11978 (stop minting org-less rows) are decided by ADR-0131 D5 — read both cards before starting, and close them by pointer in this PR if nothing survives them.Acceptance. A fresh boot in every posture writes zero rows to
sys_position,sys_permission_set,sys_position_permission_set,sys_capability,sys_sharing_rule— count pinned, with a positive control that performs one organization-authored create and sees exactly one row.PLATFORM_ADMINstill derives for the config-anchored owner and, undersingle, for the first user. Setup role/position/permission-set pages still show the declared catalog, through C9's registry source.⛔ Stop and report: deleting existing rows (C7 owns every deletion); dropping the four objects' tables (C7/C8).
Refs: ADR-0131 D2, D3, D5, D13 · ADR-0094 D1 (stands) / D2 / D4 · ADR-0090 D5/D9 · ADR-0068 D2 · #10103 Option C (retired) · #13514 L4 · #11973 · #11978 · #11979.