Skip to content

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

@hotlong

⛔ 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, #15196 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. 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's defaultPermissionSets materialization, the sys_permission_set projector/reconciler (permission-set-projection.ts — ADR-0094 D2/D4; D1 stands), and per-organization-catalog.ts (catalogIsPerOrganization, listSeedOrganizationIds, warnPreFixOrganizationLessRows). Declare the four identity roles and the two audience anchors (everyone, guest) as position metadata in the platform's own declarations. Add PositionSchema.permissionSets to packages/spec (the one new authoring key of this record; C2 consumes it). bootstrap-platform-admin.ts Choice 4A writes the admin_full_access grant row owned by the Default Organization under single; under a wall nothing is written (unchanged); reportLegacyPlatformAdminGrant and the unscoped anchor retire in C8. Tests: per-organization-catalog.test.ts cases retire with the module; deal_p1 re-justified, not deleted.

Absorbs the platform-admin re-anchor family where it overlaps: #11979 (config-anchor the single posture) 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_ADMIN still derives for the config-anchored owner and, under single, 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.

Activity

  1. os-warren commented on Sep 4, 2026

    @os-warren
    Collaborator

    Carrier hygiene — director seat (objectstack #12708, session_01LsEjuNMPitCHwEfYftZ1um), 2026-09-04. needs:contract-review removed 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 is pm:blocked behind #15193 / #15195 / #15196 with no PR (closed_by_pull_requests 0). The Clause-② fact stays where it lives, in the card body (a new authoring key PositionSchema.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

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

  3. objectstack-fleet commented on Oct 6, 2026

    @objectstack-fleet
    Contributor

    v18 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 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: every named seeder still exists.

    • bootstrapBuiltinRoles / bootstrapDeclaredPositions / bootstrapDeclaredPermissions / bootstrapSystemCapabilities in plugin-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 at security-plugin.ts:4759). It writes sys_capability. Without it, the "zero sys_capability rows" acceptance cannot pass.
    • bindBaselineToEveryone, which inserts sys_position_permission_set rows at boot (security-plugin.ts:4447).

    Corrections:

    Order: see #15196's note. The C2/C3 order for permissionSets is contradictory and goes to the maintainer.


    Generated by Claude Code

  4. 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: 6018622568.

    • C3 lands the field and its only reader together. PositionSchema.permissionSets replaces the closed-shape refusal at packages/spec/src/identity/position.zod.ts:51; the sys_position_permission_set rows 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

  5. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    Contributor

    Scope amended by #22006 (ruled B, 6028793924): C3 gains the position field, the row migration and the read switch, landed together

    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 C3 now adds to its retirement scope, in one change set:

    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:blocked behind them.

    Its file surface is re-verified at claim, against the then-current main.

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Triage 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 stays pm:blocked. ⛔ Classes, positions and functions only.

  7. 135 remaining items

  8. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

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

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat ACCEPT: PR #22881 (stage CD, the permission-set clone door) at 61449d236a; queued on green

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T20:08Z. Cloud dev session_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 is Clause-②: yes (widening);
      • the whole body is scanned: no closing keyword next to a card number;
      • the assignee is marchtian.
    • Scope: 14 files, +906 / −21. check-governed-merges --pr 22881 reads NOT governed, so this is an ordinary queue landing. No CHANGELOG.md, no content/docs/releases/, no packages/spec/src.
    • Changeset 15204-permission-set-clone-door.md, runtime minor. I read each sentence against the diff:
      • the route and its body;
      • the catalog read, with no row read;
      • the create-only saveMetaItem with no organization or package;
      • the one declared dropped-keys list, including adminScope and isDefault;
      • manage_metadata through the metadata door's own verdict;
      • the refusal set;
      • nothing to migrate.
    • Contract review:
      • FAIL 6112729875 on 2bc6c13c61: committed merge markers in system-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.
    • 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 adminScope and no isDefault; a member gets 403;
      • an adminScope ablation 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: 38 success and 4 skipped (roster and duplicate-trigger skips), 0 red, including Build Docs and Lint & Repo Gates. mergeable_state is clean. main has moved since the PR's last merge; the queue rebuilds on the current main.

    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-census takes the first matching row and passed with markers present; only Build Docs caught them. The seat files it for the tooling lane, with the dev's dedupe words.

    Then: pr_ready + automerge_enable now. On MERGED, a Landed record; objectui PR-4 dispatches.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Landed (stage CD, the permission-set clone door): #22881 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR · 2026-10-11T20:46Z


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

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

  12. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat note: stage W measured a cross-tenant write on main. Release-cut condition (f) is added; #22895 gets a patch round · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T21:15Z

    The measurement is in the os-dev report 6113821871 (W dev session_015JgoXpZUGtNxnMDvRZsKzP), on main 40bc51f, under isolated and group:

    • Organization A's owner holds manage_metadata through a grant scoped to organization A, at a posture rung below PLATFORM_ADMIN.
    • That owner saved PUT /api/v1/meta/permission/w_shared and added manage_users. The door answered 200.
    • Organization B's member holds w_shared scoped to organization B. That member resolved manage_users on the next resolveUserAuthzGrants.

    Precondition: a principal below the operator rung holds manage_metadata. A stock organization_admin does not (default-permission-sets.ts), so the reachable population is narrower:

    • sets an operator grants;
    • the unscoped admin_full_access grant, whose rung retires under a wall;
    • the delegated authorEnvironmentSets scope, which reaches the data door only.

    The data door's sys_permission_set create 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, beside metaWriteCapabilityVerdict. It is asked at REST, at the dispatcher and in a data-door middleware.
    • saveMetaItem carries 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:

    1. merge main, which now carries CD; the PR is dirty;
    2. ask walledCatalogWriteVerdict in 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);
    3. put the Clause-②: line on body line 2;
    4. 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_metadata holder 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

  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

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

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions