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. 189 remaining items

  8. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    W3 cross-tenant: publish-drafts YES · rollback YES · commits/:commitId/revert YES · duplicate NO (grants; a tenant catalog creation that splits the /meta read) · also measured: revert NO · adopt-orphans NO

    {
      "w3_cross_tenant": "publish-drafts YES · rollback YES · commits/:commitId/revert YES · duplicate NO (grants), but a tenant catalog creation that splits the /meta read · also measured: /packages/:id/revert NO (grants; discards the operator's pending catalog drafts) · adopt-orphans NO (grants; moves environment catalog rows into the tenant's package)",
      "issue": 15204,
      "stage": "W3 (the remaining /packages write doors under a wall), measure-only",
      "status": "measured",
      "branch": "none (measured in the W2 worktree at b7e2eaf5f0 = main e4ad6776d1 + #22935; scratch harness, not committed)",
      "pr": null,
      "session": "session_015JgoXpZUGtNxnMDvRZsKzP",
      "harness": "The W2 harness (6114989076 / 6117573171): bootStack(crm, multiTenant posture-only, + MarketplaceInstallLocalPlugin), with W_POSTURE=isolated, group and single. Org A's owner holds manage_metadata through an org-A-scoped grant (rung TENANT_ADMIN; under single, MEMBER). B holds w3_set in org B. The operator creates writable runtime package com.w3.rt (POST /packages 201), saves w3_set into it as a draft (PUT /meta/permission/w3_set?package=com.w3.rt&mode=draft, 200 state=draft) and publishes it (POST /packages/com.w3.rt/publish-drafts, 200, commit c1 'apply'), so w3_set is active with systemPermissions []. Isolated and group gave identical answers at every step.",
      "per_door": {
        "publish-drafts (POST /packages/:id/publish-drafts)": {
          "cross_tenant": "YES",
          "precondition": "an operator-authored pending catalog draft in a writable package. Reachable: Studio's draft-then-publish flow (ADR-0033) leaves exactly this state between the save and the publish, and W lets the operator, not the tenant, author it.",
          "measured": "The operator drafts w3_set with systemPermissions [manage_users]. B before: sys has no manage_users. A POST /packages/com.w3.rt/publish-drafts: 200, publishedCount 1, a new 'apply' commit. The catalog w3_set now has sys [manage_users]. B after: sys includes manage_users."
        },
        "rollback (POST /packages/:id/rollback {commitId})": {
          "cross_tenant": "YES",
          "precondition": "a writable package with two or more 'apply' commits touching a catalog item, i.e. any operator who published a catalog package twice. Commit ids are readable by any studio.access/setup.access holder through GET /packages/:id/commits.",
          "measured": "After the publish above (B holds manage_users), A POST /packages/com.w3.rt/rollback {commitId: c1}: 200, revertedCommits [the manage_users commit]. The catalog w3_set is back to []. B after: manage_users gone."
        },
        "commits/:commitId/revert (POST /packages/:id/commits/:commitId/revert)": {
          "cross_tenant": "YES",
          "precondition": "one operator 'apply' commit that touched a catalog item. By reading: the door passes only commitId to revertCommit; the :id segment is never read, so the commit can belong to any package.",
          "measured": "The operator re-publishes w3_set with [manage_users] (commit c3; B gains manage_users). A POST /packages/com.w3.rt/commits/c3/revert: 200, reverted [{permission w3_set restored}], a new 'revert' commit. The catalog w3_set is back to []. B after: manage_users gone."
        },
        "duplicate (POST /packages/:id/duplicate {targetPackageId})": {
          "cross_tenant": "NO change to B's grants measured, but it is a tenant catalog CREATION (D3 refuses those) and it splits the read surface",
          "precondition": "a writable base package holding a catalog item (no draft or commit needed)",
          "measured": "A POST /packages/com.w3.rt/duplicate {targetPackageId: com.w3.dup}: 200, copied [{permission w3_set}]. sys_metadata then holds two active rows, w3_set@com.w3.rt and w3_set@com.w3.dup; the permission name is NOT re-namespaced (only object names are). B unchanged. Follow-up (isolated): the operator publishes w3_set [manage_users] in com.w3.rt. The catalog resolver and B follow it (B gains manage_users), but GET /meta/permission/w3_set now serves A's copy (_packageId com.w3.dup, systemPermissions []). The metadata door shows the operator a definition that is not the one enforced."
        },
        "revert (POST /packages/:id/revert, discard drafts) — not in ③ 2, same gate": {
          "cross_tenant": "NO change to B's grants",
          "measured": "The operator drafts w3_set [manage_users, manage_metadata]. A POST /packages/com.w3.rt/revert: 200. The package's drafts afterwards: []. A tenant discards the operator's pending catalog work."
        },
        "adopt-orphans (POST /packages/:id/adopt-orphans) — not in ③ 2, same gate": {
          "cross_tenant": "NO change to B's grants at the step",
          "measured": "The operator saves environment set w3_env (no package). A creates its own runtime package com.w3.apkg (201) and POST /packages/com.w3.apkg/adopt-orphans: 200, reassignedCount 2. It moved w3_env AND w3_meta_author (the operator's set that grants A its manage_metadata) into A's package. Under a wall, A's DELETE of that package is then refused by #22935 (403 PERMISSION_DENIED). Under single it answers 200, removing those environment sets."
        }
      },
      "single_control": "Under single every door behaved the same for the MEMBER-rung manage_metadata holder: publish-drafts gave B manage_users, rollback and commit revert removed it, duplicate copied the set, adopt-orphans moved the sets, and DELETE answered 200 (#22935 is inert under single). That is the single-tenant behaviour, unchanged.",
      "placement": "One refusal per door in packages/runtime/src/domains/packages.ts, after requireManageMetadata, asking walledCatalogWriteVerdict first (the #22935 shape: when it admits, read nothing). Then:\n(a) publish-drafts and revert: refuse when the package holds a catalog-type DRAFT row (the rows these verbs promote or discard);\n(b) rollback: refuse when any commit it would revert touched a catalog type;\n(c) commits/:commitId/revert: refuse when THAT commit touched a catalog type, read from the commit, never from the URL :id;\n(d) duplicate: refuse when the source holds catalog rows (a creation);\n(e) adopt-orphans: refuse when the orphans include catalog rows, or leave catalog types out of the adoption under a wall.\nA shared helper generalising #22935's refuseWalledCatalogPackageDelete (package -> stored catalog types; commit -> touched catalog types) keeps it one predicate and one sentence. The commit-level readings need the commit's item list (revertCommit already reads it).",
      "release_17_7_0": "Read at 4e4e881427: publish-drafts (:1466), rollback (:1859), commits/:commitId/revert (:1837), duplicate (:1940), adopt-orphans (:1908) and revert (:1882) all exist, each gated by requireManageMetadata alone (:302, capability or isSystem, no rung). The protocol carries publishPackageDrafts, revertCommit, rollbackToPackageCommit, duplicatePackage and reassignOrphanedMetadata. revertStoredPackage is absent: 17.7.0's /revert calls metadataService.revertPackage directly. So the same doors are there. Read only; 17.7.0 was not booted for W3.",
      "tests": "Measurement only: a scratch dogfood file, removed from the tree after each run (worktree clean, HEAD b7e2eaf5f0). Runs: W_POSTURE=isolated, group and single, each exit 0 (logs, no assertions), plus one isolated follow-up for duplicate's resolution.",
      "mcp_calls": "0.",
      "api_writes": "1 relay stroke: this comment.",
      "open_questions": [],
      "out_of_scope_findings": [
        "by reading: POST /packages/:id/commits/:commitId/revert never reads :id, so the URL's package names nothing the door checks (requireWritablePackage is not asked either). · dedupe words: commit revert ignores package id url · noted, not filed (part of W3's placement (c))"
      ]
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat note: W3 measured cross-tenant at three more package doors → the build is dispatched · release-cut condition (f) becomes W + W2 + W3 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T04:27Z

    The measurement is 6117740670 (session_015JgoXpZUGtNxnMDvRZsKzP), taken on main e4ad6776d1 plus #22935 b7e2eaf5f0, under isolated and group (identical answers at every step). It answers the escalation in contract review 6117680834 ③ 2.

    Cross-tenant (organization A's manage_metadata holder changes what organization B's members resolve):

    • publish-drafts: A publishes the operator's pending catalog draft, and B gains manage_users.
    • rollback: A rolls back to an earlier commit, and B loses manage_users. The precondition is any second publish; commit ids are readable at GET /packages/:id/commits.
    • commits/:commitId/revert: A reverts the operator's commit, and B loses manage_users. The door never reads the URL's :id, so the refusal must judge the commit's own package and items.

    Not cross-tenant on grants, but a tenant writing the environment catalog, which D3 refuses ("tenants are refused creation and may only assign", 6070695107):

    • duplicate: A's copy re-declares the operator's set unnamespaced. GET /meta/permission/:name then serves A's frozen copy.
    • revert (discard drafts): A discards the operator's pending catalog draft.
    • adopt-orphans: A adopts environment sets into its own package.

    Controls: single is unchanged for a member-rung holder, and the operator is admitted.

    On 17.7.0 (4e4e881427, read only): all six doors exist behind requireManageMetadata alone. #22938 is told.

    Answer: build W3. No new ruling is needed: ADR-0131 D3 names this refusal ("read-only under a wall … the server refuses anyway"), as it did for W and W2.

    Release-cut condition (f) of 6102862135 now reads W (#22895, merged) + W2 (#22935) + W3. Whether the first next also waits for W3 is the maintainer's call. It is asked in the seat's session; the rulings stand at 6117174845 and 6117531327.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat note: the console pin waits for #22874 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T04:59Z

    #22939's folded head e486abb9c8 was refused by check:single-claim-paths: .objectui-sha is claimed by #22874, the spec seat's ADR-0131 C5 stage S2 rename allowOrgOverride → allowEnvironmentOverlay, which moves the pin to objectui e5311165a8d6. The ordering is real, not only a gate:

    Order now:

    1. feat(plugin-security,platform-objects): grant forms pick a position or permission set by name from the catalog (#15204 B2) #22939 lands phase 2 only: the pin commit is reverted by a new commit, with no force-push.
    2. feat(spec,metadata-protocol)!: rename the type registry's allowOrgOverride to allowEnvironmentOverlay, on the registry and on GET /meta/types #22874 lands with its pin at e5311165. That console carries objectui PR-4 and [finding] check-drift-comment.mjs has no fixture that reaches the SDK-bridge line, so that bullet's rendered bytes — the cause split included — are pinned only by source grep #12148.
    3. A follow-up pin PR from main hops e5311165 → 088e0f705 or later, carrying Q3b and PR-5, and re-measures only that hop.

    D (#22900) is gated by #22939 phase 2. The first next's "new console" condition (6117531327) now runs through #22874 and the follow-up. The maintainer is told.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Pointer from #22937 (C7b build-ahead): PR 1 of 2 is open as draft PR #22959 · domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T05:34Z. ⛔ Not a claim on this card. Nothing here is asked of the epic seat.

    • What it adds to @objectstack/core: two pure functions in new files in packages/core/src/security/, beside convertPositionBindingRows, and exported from security/index.ts:
      • permissionSetDefinitionFromRow(row) gives the same output as permissionSetBodyFromRow in plugin-security/src/permission-set-projection.ts;
      • positionDefinitionFromRow(row) gives the same output as positionBodyFromRow in plugin-security/src/position-write-through.ts, which is the mapping the row-only position backfill applies.
        The row types PermissionSetRowForDefinition and PositionRowForDefinition are exported with them. Both functions return a string-keyed record of unknown values, the same type the plugin's copies return, so 7a/7b can switch to them and delete their copies without a cast.
    • What it leaves alone: it edits nothing under packages/plugins/plugin-security/** and changes no behaviour, because nothing calls the functions yet. The backfill's name-grammar predicate and its rule that rows of one name must agree stay in the plugin.
    • S8 overlap: the export block sits after convertPositionBindingRows's block in security/index.ts. That is a different hunk from S8's removal of the row-active.ts block, and neither new file imports row-active.ts.
    • Landing: contract-tier review is pending, then the queue. PR 2 (os migrate organization-ownership --apply) is claimed after PR 1 lands, is based on main, and stays a draft until stage 8 is on main, as C7b build-ahead: os migrate organization-ownership --apply and E5's pure row→definition mappers in core (drafts from main; the --apply PR lands after #15204 stage 8) #22937's body says.
  12. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat note: #22874 is merged, and the console pin hop is dispatched · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T06:15Z

    #22874 merged as e23eccb164, so main's .objectui-sha reads objectui e5311165a8d6. That is step 2 of the order in 6118044018.

    Step 3, dispatched now:

    This PR, W2 #22935 and #22939 are the first next's remaining items (6117531327). W2 and #22939 are held on #22949's suite-duration ceilings (6118632107, 6118649663).


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15204,
      "status": "done",
      "branch": "claude/issue-15204-s8t-tests (S8-T, draft PR) + claude/issue-15204-s8-catalog-objects (S8, pushed, no PR while #22900 is unmerged)",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22962",
      "session": "session_01MGoWgeE8rhsrig77V4oUZn",
      "premise_still_valid": true,
      "summary": "Stage 8 split as dispatched. S8-T (#22962, draft, base main eab7236ff7, head 813dfed93e74e6bb2ce7b31ce15e9d23f853f55d) holds only test deletions/edits plus a test-only testkit for the retired table shapes and two new pins (E6 fresh-boot golden; E1 metadata-door-reaches-catalog dogfood); green on main alone. S8 (head dbae752f6e94a205ec7898849b88b53d47f92c1f = main + merge of D fcaedfdaa1 + one commit) retires the four objects and their translations/references, unwires the projector, both write-throughs and the position backfill (E1), makes permission_set_id Field.text (E3), drops the legacy-row read from the first-admin promotion while keeping both pins (E6), revokes uninstall grants by the package's declared set names, and carries the listed residue items. E1 premise verified: a metadata-door permission save reaches the catalog via applyRegistryWriteThrough, not syncEvaluatorRegistry (dogfood pin green with the projector wired on main and unwired on main+D+S8).",
      "s8_head_sha": "dbae752f6e94a205ec7898849b88b53d47f92c1f",
      "s8_net_lines_vs_main_plus_d": "72 files, +443 / -3,045 = 3,488 changed lines (git diff 1d5c2b2 --shortstat; 1d5c2b2 = merge of main eab7236ff7 + D fcaedfdaa1)",
      "s8t_pr": 22962,
      "s8t_net_lines_vs_main": "88 files, +1,122 / -3,946 = 5,068 changed lines",
      "s8_pr_body": "Clause-②: yes\n\nPart of #15204 (C3 stage 8, the code half: S8). Lands after #22900 (D) and after the S8-T test PR #22962. #15204 remains open (7a/7b dead-code deletions, C7b data rewrite, 8-post residue and docs sweep follow).\n\n## What this changes\n\nThe four security-catalog row objects - `sys_position`, `sys_permission_set`, `sys_position_permission_set`, `sys_capability` - are no longer registered (ADR-0131 D3). Their data/REST routes and Setup object pages go; tables and rows stay for the organization-ownership migration (ADR-0131 D10).\n\n- **E1:** the `permission` mutation projector and its reconcile, the `sys_permission_set` and `sys_position` data-door write-throughs and the row-only position backfill are unwired. A metadata-door save reaches the catalog through the door's own registry write-through (pinned by `permission-metadata-door-reaches-catalog.dogfood.test.ts` in #22962). The modules stay for 7a/7b to delete.\n- **E3:** `sys_user_permission_set.permission_set_id` is `Field.text` (maxLength 255): same column, ids, indexes.\n- **E6:** the first-admin promotion reads no catalog row. Leg C now reaches an unscoped human grant that names no set and carries a legacy `permission_set_id`; both pins hold (fresh-boot golden in #22962; `grant-readers-unnamed-grant`).\n- **Uninstall cleanup:** revokes every `sys_user_permission_set` grant naming a permission set the package declared. `deletePackage` and the marketplace uninstall read the names off the package before withdrawal and pass them as `declared` (`UninstallCleanup`, `declaredCatalogItemsOf` in `@objectstack/metadata-protocol`). The row legs are gone; the by-id leg is C7b's.\n- **Carried items:** Setup nav entries open `/apps/setup/metadata/<type>?scope=environment`; the grants-cache watched set, plugin-sharing `bypassObjects`, organization_admin/showcase object-permission entries, `SystemObjectName.POSITION|PERMISSION_SET`, platform-object name lists and tenancy entry drop the four names; the lint `capability-reference-unknown` seed-row source goes; refusal prose names the clone door instead of `POST /api/v1/data/sys_permission_set`; settings/spec help text updated; `isRowActive` deleted.\n- **Regenerated:** plugin-security translations, platform-objects page bundles, spec reference docs, tenancy and tenant-audit censuses, the keyed-text-bounds `MEASURED` record (floor 235->210, re-measured at eab7236ff7); platform-checklist item `access-security.packaged-permission-set-lifecycle` retired; ADR-0087 semantic entry `catalog-row-objects-retired`.\n\n## Acceptance notes\n\n- Not here: `sys-user.page.ts` add pickers (B2 pickers PR, lands first); comment/description residue, `delegated-admin-gate` and SP catalog gates, `SYSTEM_ROW_PROVENANCE` (8-post); dead modules (7a/7b); CLI org-ownership inventory and the id-to-name rewrite (C7b); docs sweep (separate dispatch).\n- `lint/src/validate-capability-references.seed-row.test.ts` is a pin of the new behavior that cannot pass on `main` alone, so it rides here, not in #22962.\n\n## Verification\n\nOn `main` + D + #22962 + this branch: dogfood 245 files / 1,976 passed; package suites of spec, core, lint, platform-objects, hono-server, metadata-protocol, objectql, service-settings, plugin-sharing, plugin-approvals, plugin-security, rest, plugin-auth, runtime, cloud-connection, verify, app-crm, client, organizations, app-showcase, cli unit all green. See the os-dev-report on #15204.\n\n---\n_Generated by [Claude Code](https://claude.ai/code/session_01MGoWgeE8rhsrig77V4oUZn)_\n",
      "s8_changeset": "---\n'@objectstack/plugin-security': minor\n'@objectstack/platform-objects': minor\n'@objectstack/spec': minor\n'@objectstack/core': minor\n'@objectstack/metadata-protocol': minor\n'@objectstack/cloud-connection': patch\n'@objectstack/objectql': patch\n'@objectstack/plugin-sharing': patch\n'@objectstack/lint': patch\n'@objectstack/service-settings': patch\n---\n\nfeat(plugin-security)!: the four security-catalog row objects are retired — `sys_position`, `sys_permission_set`, `sys_position_permission_set` and `sys_capability` (ADR-0131 D3)\n\nClause-②: yes (narrowing)\n\n[line 18 in the file: the ADR-0087 marker `adr-0087: registered catalog-row-objects-retired`, written as an HTML comment; shown here as text]\n\n**BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes.\n\nPositions, permission sets and capabilities live in the security catalog — the environment registry, fed by package declarations and by environment metadata saved through `/api/v1/meta` (ADR-0131 D3). The four row objects that mirrored them are no longer registered, so their data and REST routes and their Setup object pages are gone. No table is dropped and no row is deleted.\n\nFROM → TO:\n\n- `GET|POST|PATCH|DELETE /api/v1/data/sys_permission_set` → `GET|PUT|DELETE /api/v1/meta/permission/:name`; to copy a set, `POST /api/v1/security/permission-sets/:name/clone` with `{ name, label }`.\n- `… /api/v1/data/sys_position` → `/api/v1/meta/position/:name`. A position's permission sets are its definition's `permissionSets`.\n- `… /api/v1/data/sys_position_permission_set` → the position definition's `permissionSets` (`PUT /api/v1/meta/position/:name`).\n- `… /api/v1/data/sys_capability` → read-only `GET /api/v1/meta/capability`; a package declares a capability with `defineCapability` (`stack.capabilities`).\n- Setup → Access Control → Positions / Capabilities / Permission Sets now open Setup's catalog lists (`/apps/setup/metadata/<type>?scope=environment`) instead of the object pages; the `sys_position` record page (`SysPositionDetailPage`) is removed.\n- An object permission on one of the four names in a permission set grants nothing and can be deleted.\n\nWhat else changes:\n\n- `sys_user_permission_set.permission_set_id` is a plain text column now, not a lookup: same column, same stored ids, same indexes. It is not written and grants nothing; a grant names its set by `permission_set`.\n- A permission set saved through the metadata door is written to no row: the `permission` mutation projector, the `sys_permission_set` and `sys_position` data-door write-throughs and the row-only position backfill are no longer registered.\n- On a fresh `single`-posture database the first human is promoted to platform administrator without reading any catalog row. An unscoped grant written before `sys_user_permission_set.permission_set` existed (it names no set) still withholds the promotion until the organization-ownership migration names it.\n- Uninstalling a package revokes every grant naming a permission set the package declared. The uninstalling door reads those names off the package before withdrawing it and hands them to each uninstall cleanup as `declared` (`UninstallCleanup`, `@objectstack/metadata-protocol`; `declaredCatalogItemsOf` is exported).\n- `capability-reference-unknown` no longer counts a `sys_capability` seed row as a declared capability — no runtime reader consults one. Declare the capability with `defineCapability`.\n\nRemoved exports, no replacement:\n\n- `@objectstack/plugin-security`: the object definitions `SysPosition`, `SysPermissionSet`, `SysPositionPermissionSet`, `SysCapability` (from `./objects`); `securityObjects` lists the remaining three.\n- `@objectstack/platform-objects/pages`: `SysPositionDetailPage`.\n- `@objectstack/spec`: `SystemObjectName.POSITION`, `SystemObjectName.PERMISSION_SET`; the four names leave `PLATFORM_OBJECTS_BY_PACKAGE['plugin-security']` and `PLATFORM_PROVIDED_OBJECT_NAMES`.\n- `@objectstack/core`: `isRowActive` and `ActivatableRow` — a catalog row's `active` column decides nothing; deactivation is the activation ledger's (`sys_metadata_activation`).\n",
      "tests": "Integration tree main+D+S8-T+S8, via os-verify-lock: dogfood full 245 files, 1976 passed, 9 skipped, 0 failed. Package suites (VERDICT exit 0, held 49m39s): spec 648 files/19394 tests; core 93/2376; lint 138/6530; platform-objects 69/1078; hono-server 28/329; metadata-protocol 226/28171; objectql 401/7817; service-settings 42/755; plugin-sharing 41/1007; plugin-approvals 73/1035; plugin-security 184/3808 (45 skipped); rest 273/5721; plugin-auth 136/2780; runtime 354/5108; cloud-connection 43/517; verify 32/250; app-crm 6/46; client 51/653; organizations 12/152; app-showcase 33/409; cli unit 282/4262. S8-T on main alone: 71 touched/added test files in 11 packages green; typecheck green. Ablation (E6): putting back D's bootstrap-platform-admin.ts on the int tree turned the fresh-boot golden red (adminPromoted: false); restored -> green; git diff HEAD empty. Gates: S8-T 89 derived gates green except engine-split-ratio and plugin-teardown self-test (shallow clone, NOT MEASURED) and check:dual-build-cjs-loads (needs full build, NOT MEASURED). S8 146 derived gates: adr-0087 registration, keyed-text-bounds, test-source-alias, platform-checklist (8 reds identical on main, pre-existing), i18n/i18n-coverage, tenancy and tenant-audit censuses green; after spec rebuild check:api-surface, browser-reachable-entries, dual-source-exports, entry-nameability, exported-any, skill-examples exit 0; check:generated red only on check:test-typecheck for spec system-names.test.ts (2 type errors), which S8-T fixes - expected on S8 alone, green once #22962 is on main. engine-split-ratio / teardown self-test NOT MEASURED (shallow).",
      "mcp_calls": "0",
      "api_writes": "3 - (1) pr_create via fleet-write relay, request fw-20261012T053137Z-86460d -> #22962 (body read back byte-identical, assignee marchtian); (2) label-write --assign marchtian, relay request fw-20261012T053216Z-4d0ce3 -> labels_add skip-changeset on #22962; (3) this os-dev-report comment on #15204 via the relay. git push (not a REST write): both branches; both were squashed once into a single hook-checked commit and re-pushed with --force-with-lease (branches mine, no PR existed for S8; S8-T before its PR opened; trees verified identical).",
      "open_questions": [
        {
          "question": "Both halves exceed the 3,000-changed-line line: S8 3,488 vs main+D, S8-T 5,068 vs main (S8-T is ~78% deletion; S8 ~87% deletion). Land as is, or split?",
          "options": [
            {
              "option": "A - land both as is with an explicit over-limit approval; human merge",
              "axes": {
                "business_need": "The retirement is one behavioral unit: objects, nav, E1 unwiring and E3 must land together or the Setup nav points at dead pages / the projector writes into an unregistered object. Measured: the bulk is deletion of declarations, generated translations and censuses.",
                "long_term": "One atomic cut matches 'retire immediately'; no intermediate state where rows and registry both serve.",
                "ai_error_proofing": "Reviewers read mostly deletions; the risky lines (E6 leg C, uninstall declared, E1 unwire) are small and pinned.",
                "startup_focus": "No extra PRs or transition states."
              }
            },
            {
              "option": "B - split S8 into S8a (E6 + uninstall declared + protocol + lint + residue, landable on main+D first) and S8b (objects + translations + nav + E1 + E3); split S8-T into deletions / edits",
              "axes": {
                "business_need": "No user-facing gain; S8a alone changes nothing observable except uninstall and lint.",
                "long_term": "Adds an intermediate main where uninstall revokes by name while rows still exist - harmless but a state nobody needs.",
                "ai_error_proofing": "Smaller diffs are easier to review; but four PRs need ordering and three more integration rounds, which is where AI sequencing errors happen.",
                "startup_focus": "Spends more cycles on process, contrary to startup focus."
              }
            }
          ],
          "recommendation": "A: the long-term reading favours one atomic retirement; the size is deletion-dominated and the logic-bearing changes are pinned by tests in #22962."
        },
        {
          "question": "PR-4's note: does any producer of a managed_by: 'platform' value on catalog rows survive stage 8?",
          "options": [
            {
              "option": "A - answer: no runtime producer survives; leave the inert SYSTEM_ROW_PROVENANCE gates to 8-post",
              "axes": {
                "business_need": "Measured: after S8 the four objects are unregistered and D already removed the seeders; the only remaining writers are the S8-T testkit fixtures. sys_user_permission_set/sys_user_position carry no such producer.",
                "long_term": "The inert gates are dead code for 8-post to delete with the rest of the residue.",
                "ai_error_proofing": "No declared-but-unhonored surface is added.",
                "startup_focus": "No new work in stage 8."
              }
            }
          ],
          "recommendation": "A (informational): no producer survives stage 8."
        },
        {
          "question": "S8 depends on two things outside both PRs: the B2 pickers PR (claude/issue-15204-c9-grant-pickers) must remove the sys-user.page.ts add pickers that target sys_permission_set/sys_position, and the objectui pin must move to a main that has the /apps/setup/metadata/<type>?scope=environment catalog pages (objectui main 2498a07 has them; the pin 4997995022f8 was read).",
          "options": [
            {
              "option": "A - open S8 only after D, #22962, the B2 pickers PR and an objectui pin bump are on main",
              "axes": {
                "business_need": "Without both, Setup shows a picker for an unregistered object or a nav link to a missing page.",
                "long_term": "Keeps each concern in its owning PR.",
                "ai_error_proofing": "Sequencing is mechanical and checkable from main.",
                "startup_focus": "No extra code."
              }
            },
            {
              "option": "B - fold the picker removal into S8",
              "axes": {
                "business_need": "Same end state.",
                "long_term": "Duplicates B2's ownership.",
                "ai_error_proofing": "Two PRs editing one file invites conflict.",
                "startup_focus": "Adds lines to an already over-limit PR."
              }
            }
          ],
          "recommendation": "A."
        }
      ],
      "out_of_scope_findings": [
        "carrier: 8-post · descriptions still citing the retired objects in sys-user-position.object.ts, sys-invitation.object.ts, sys-audience-binding-suggestion.object.ts and spec protocol.zod.ts / eval-user.zod.ts / position.zod.ts describes; delegated-admin-gate and SP catalog gates; SYSTEM_ROW_PROVENANCE; PLATFORM_CAPABILITY_NAMES drift (left alone per dispatch) · noted, not filed",
        "carrier: 7a/7b · permission-set-projection.ts, position-write-through.ts, writesExecutedByDataDoor WeakSet are now unreferenced; scripts/tenant-audit-census.mjs carries two UNTYPED_RECEIVERS rows for them that go with the modules · noted, not filed",
        "carrier: C7b · grant-name backfill left wired (inert); auto-org-admin-grant resolvePermissionSetIdsForName tryFind (logs at debug); CLI organization-ownership inventory (raw SQL); the by-id uninstall leg · noted, not filed",
        "carrier: docs-sweep dispatch · files: content/docs/concepts/metadata-lifecycle.mdx, content/docs/data-modeling/objects.mdx, content/docs/deployment/environment-variables.mdx, content/docs/permissions/authorization.mdx, content/docs/permissions/capabilities.mdx, content/docs/permissions/delegated-administration.mdx, content/docs/permissions/permission-sets.mdx, content/docs/permissions/positions.mdx, content/docs/permissions/system-context.mdx, content/docs/protocol/backward-compatibility.mdx, docs/adr/0029, 0057, 0066, 0086, 0090, 0091, 0094, 0103, 0126, 0131 (measured 10 hand-written content/docs pages + 10 ADRs; the 4 auto-generated reference pages were regenerated in S8; the dispatch's 25/10 count did not match) · noted, not filed"
      ],
      "d_merge_recipe_for_s8t": "When #22962 merges main after D: take S8-T's deletions over D's edits; keep D's catalogPermissionSet reads in activity-text-predicate, approval-snapshot-masked-field, sharing-rule-org-less-caller, showcase-crud-persona-matrix; take S8-T's side in showcase-fls-read-mask-strip and showcase-permission-zoo; delete ensureLegacyPermissionSetRow from D's catalog-permission-set.ts; guard D's showcase-permission-seeding census with getObject('sys_permission_set'); plugin-security import conflicts keep D's seedLegacyPlatformBucket import plus the testkit import. Run on the local int tree; green.",
      "deviations": [
        "dispatch vs .claude/agents/os-dev.md: no disagreement changed an action. os-dev.md's write budget lists four writes; this run used three REST writes plus git push, within it.",
        "S8 is 'code only' per dispatch but carries one new test (lint validate-capability-references.seed-row.test.ts) because it pins new behavior that fails on main and so cannot ride S8-T.",
        "Both branches were squashed and re-pushed with --force-with-lease once (commits first made without hooks); trees identical before and after."
      ]
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Seat answer to the stage-8 report 6118994290 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T06:31Z

    Read against GitHub, not the report:

    The three open questions:

    1. Size: A. Both halves stay whole. S8-T is 5,068 changed lines and S8 is 3,488 vs main+D, so both exceed 3,000. Each lands the Tier H way: ACCEPT, needs-user-decision, the 维护者速读 comment, and then the maintainer's approval or merge. No Tier S exception applies.
    2. Surviving managed_by: 'platform' producer: A, recorded. No runtime producer survives stage 8. The inert SYSTEM_ROW_PROVENANCE gates go to 8-post with the residue.
    3. S8's preconditions: A. S8 opens only after D, test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) #22962, the B2 pickers PR feat(plugin-security,platform-objects): grant forms pick a position or permission set by name from the catalog (#15204 B2) #22939, and the objectui pin bump are on main.

    One form fix on #22962 at its next body edit: line 1 should be Part of #15204 and line 2 Clause-②: no. They are currently reversed.

    Order now: #22939 → T1 #22897 → T2 #22898 → D #22900 → S8-T #22962 → S8. #22939 is held on #22949 (the suite-duration ceilings).


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Maintainer ruling recorded: the first next waits for W3 too, with the full new console; the seat ports #22949's fix into W2 to save a queue round · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T06:48Z

    The seat asked in its session:

    • whether the first next prerelease waits for W3;
    • which console it ships with.

    It also put a speed-up to the maintainer. The maintainer answered 「等 W3,按你说的加速方案来」 ("wait for W3, and take the speed-up you described").

    What the first next now waits for. This adds to 6117174845 and 6117531327:

    So all three cross-tenant writes measured on 17.7.0 (W, W2, W3) are closed in the first prerelease. Release-cut condition (f) for the final release is unchanged (W + W2 + W3).

    The speed-up. #22949 is ruled C2 for both timing steps plus a floor (6119186511). The domain:devx lane opens one PR that changes ci.yml's two timing steps and the gate header.

    Then: when all of the above is on main, the seat posts the known-limitations notes for next. The maintainer runs cut-rc.yml.


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Ruling recorded on #22938 (comment 6119525648, batch #323 item 2, the maintainer's words verbatim 「22938 npm 不用处理吧,云版本目前只有我们自己的cloud在用」 and 「22938 cloud 等 v18」): N for npm (no 17.x advisory, no 17.7.1, no maintenance branch; #15193's rulings stand) and D 否 for cloud (it waits for v18, no one-off commit). W / W2 / W3 ship with v18 on this track. Director seat, summon #37, session_01LqNgdPZB2Jnr5HpGbaqLeB, 2026-10-12T07:14Z. ⛔ Not a claim; no label change here.

  17. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    Contributor

    Handoff record (audit only, ⛔ not a claim) · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-12T07:28Z

    The maintainer asked in this seat's session to spread the remaining work across accounts (「我是想把工作量分到别的账户」). Five follow-ons of this card leave the epic:#15194 subtree for lane PMs, each as pm:queue and priority:p1:

    Card Lane What Gate
    #22975 domain:engine stages 7a ∥ 7b → 8-post: delete the permission-set projection and the position write-through that S8 leaves unreferenced, then the four catalog gates, SYSTEM_ROW_PROVENANCE and the residue build ahead now; PRs open after S8 is on main
    #22976 domain:devx docs sweep: 10 content/docs pages draft now; PR after S8
    #22978 domain:skills ADR sweep: 10 ADRs (governed, Tier H) draft now; PR after S8
    #22979 domain:skills SKILL.md:92's no-MCP-write rule should name the rerun_failed_jobs exception none
    objectstack-ai/objectui#12200 domain:ui the position picker lists the everyone audience anchor none

    Stays with this seat:

    This seat posts "S8 on main" on #22975, #22976 and #22978 when it lands.

    Unchanged:


    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