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

  8. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15204,
      "stage": "U2 (build)",
      "status": "done",
      "branch": "claude/issue-15204-su2-suggestion-confirm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22854",
      "head": "d493e2257 (main 1eff3224d merged in, after 6a #22793 landed)",
      "session": "session_01D4f5gJKWdzmeZP57AVkRJd",
      "claim": "PM claim amendment 6108623383. Re-read before the PR write: the comments after it (6108751935, 6108782884, 6108842484, 6109155396, 6109414021, 6109497880, 6109520247) name no other branch for stage U2 and no other stage for this branch. No claim of my own; card assignee untouched.",
      "premise_still_valid": true,
      "summary": "Built #22801 -> A. A package's isDefault set now reaches every signed-in member only through one deployment-level acceptance by a manage_metadata holder (under a wall, the platform operator), and the acceptance takes effect. The security plugin re-derives the everyone definition as baseline + accepted sets (SecurityPlugin.declareEveryone) at kernel:ready and on accept, revoke, uninstall and package publish. The accept refuses forbidden-bit sets 403 PERMISSION_DENIED (ADR-0090 D7, definition side); the derivation withholds and reports a set that later fails. Reconciliation reads the everyone definition. The record is one row per suggestion owned by the Default Organization. The module no longer imports readDeclared/upsertPackagePermissionSet and no longer reads sys_position, sys_permission_set or sys_position_permission_set (6b-2 and stage 8 unblocked); declarations come from the security catalog. Measured end to end: a member's read goes 403 -> 200 on accept and back to 403 on revoke; a tenant admin's accept is 403; a forbidden-bit accept is 403; the everyone definition changes and stays the plugin's declaration, with no stored overlay and the meta door still refusing. Landing point is the producer the dispatch named; REST routes, dispatcher twin, client and spec change text only.",
      "measurements": {
        "everyone_rederive_seam": "declareEveryoneBaseline (builtin-positions.ts) re-registers the item under SECURITY_PLUGIN_ID through registry.registerItem; same package replaces its own item, no caller-reachable path. Followed by epoch.bump('metadata') so the grants caches retire. Measured in the dogfood pin: resolver grants change on the next request.",
        "default_organization": "ADR-0131 spells a deployment-level row as owned by the Default Organization (D3, D5: the single-posture platform-admin grant row). Resolved read-only as plugin-auth's boot invariant does: slug='default', else the only organization; none -> 409 SUGGESTION_STATE naming the remedy.",
        "authority_inputs": "ExecutionContext.systemPermissions includes manage_metadata (the metaWriteCapabilityVerdict input) and, under group/isolated, ExecutionContext.posture === 'PLATFORM_ADMIN' (the ADR-0126 section 5 / #15981 rung, never a position name). Measured on the booted showcase: the seeded admin passes, an org 'admin' member is 403."
      },
      "line_budget": "git diff --shortstat origin/main...HEAD at d493e2257: 23 files, +1224 / -1599 = 2823 changed lines (test deletion of the 557-line install-path suite and regenerated census files included). Under 3000.",
      "files_changed": [
        ".changeset/15204-su2-suggestion-acceptance.md (new)",
        ".changeset/15204-su2-contract-text.md (new)",
        "content/docs/permissions/authorization.mdx (2c sealed reading)",
        "content/docs/permissions/permission-sets.mdx",
        "content/docs/permissions/system-context.mdx (row 57 re-anchored)",
        "content/docs/permissions/tenant-audit-census.mdx + docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (regenerated, prose counts corrected)",
        "docs/qa/platform-checklist/areas/access-security.json (item suggested-binding-loop, revision 3)",
        "packages/cli/src/utils/organization-ownership-inventory.ts (citation text)",
        "packages/client/src/index.ts (TSDoc)",
        "packages/plugins/plugin-security/src/index.ts",
        "packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts (deployment_decision field, docs)",
        "packages/plugins/plugin-security/src/security-plugin.ts (declareEveryone, wiring)",
        "packages/plugins/plugin-security/src/suggested-audience-bindings.ts",
        "packages/plugins/plugin-security/src/suggested-audience-bindings.test.ts (rewritten)",
        "packages/plugins/plugin-security/src/suggested-audience-bindings-install-path.test.ts (deleted)",
        "packages/plugins/plugin-security/vitest.config.ts (comment)",
        "packages/qa/dogfood/test/suggested-binding-acceptance.dogfood.test.ts (new)",
        "packages/rest/src/rest-server.ts (docblock, route summaries)",
        "packages/runtime/src/domains/security.ts (header comment)",
        "packages/spec/src/contracts/security-service.ts (TSDoc)",
        "packages/spec/src/security/permission.zod.ts (TSDoc)",
        "scripts/engine-double-contract.pinned.json (two rows of the deleted suite)"
      ],
      "tests": "All at final head d493e2257 unless noted. plugin-security: vitest 196 files passed, 3992 passed / 45 skipped; typecheck (incl. test typecheck) exit 0. Full dogfood suite (packages/qa/dogfood pnpm test): 247 files passed / 1 skipped, 2002 tests passed / 9 skipped; the new pin 5/5. cli: typecheck exit 0, unit 281 files / 4185 passed. spec: check:generated 'All 14 generated artifacts are up to date', typecheck exit 0. Pre-merge at d99797c (merge brought no change to these packages): rest 272 files / 5245 passed, runtime 349 / 5038, client 51 / 653, spec src/contracts+src/security 58 / 762, typecheck exit 0 for all four. Earlier full cli unit run under the 125-gate battery load: 1 failed (hook-timeout-override-refusal CONTROL, 5s timeout, a spawn test), 4173 passed; that file alone: 4/4 passed; the final-head full run above is green. ABLATION (one-time, not kept): scripts/ablation-replace.mjs replaced 'await deps.rederiveEveryone();' in confirmAudienceBindingSuggestion (anchor 1 -> 0, blob 1c0b0a67 -> 10a90e69), plugin-security rebuilt, ablation-dist-preflight: marker present in 2 built files; dogfood accept case went RED ('expected false to be true' on bindingCreated), 1 failed / 4 passed; restore: blob == HEAD 1c0b0a67, git diff HEAD empty; rebuild + preflight --absent: marker absent from all 6 built files, tree clean. eslint --no-inline-config over the 13 touched TS files: exit 0 (a pre-check, not the CI-owned pnpm lint).",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at d493e2257: 125 derived (same 125 as before the merge). All 125 run at d493e2257: 124 exit 0, 1 exit 1 = pnpm check:platform-checklist, 7 problems (ABSENT SYMBOL anonymousFormIntakeOrgScopeRefusal / anonymousFormIntakeReopenRefusal x2 / envWideRawViewRows x2 in packages/metadata-protocol/src/protocol.ts, canEdit in service-storage attachment-access-hooks.ts, attachments-storage anchor count 27 against a floor of 28) - the SAME 7 on base c4e7fa5 measured in a throwaway base worktree; none in the item this PR edits. Reconcile: --ran with exit codes -> 'Run reconciliation - 125 derived, 125 run, 0 NOT-MEASURED, 0 UNRUN' and '0 NOT-MEASURED (a DERIVED zero - all 125 recorded an exit code and none of them is 3)'. Repaired on the way: check-adr-0087-registration (changeset wording, see deviations), check-tenant-audit-census (regenerated: two retired write sites, 233 -> 231 on the merged base; hand-written prose counts corrected), check-system-context-census (row 57 -> assertDeploymentAuthority). Prerequisite refusals in the first battery (exit 3: check:skill-examples, check:dual-build-cjs-loads, check-plugin-teardown-shape --self-test) were cleared by building / fetching the pinned fixture commit, and are exit 0 in the final battery.",
      "clause_2": "Clause-②: yes (narrowing). Widening: a confirm now grants (the accept takes effect). Narrowing: tenant administrators lose list/confirm/dismiss; exports removed. Changeset levels: plugin-security minor with BREAKING; spec/client/rest/cli patch (text only); runtime comment only, no changeset. Same-head contract-tier review is owed (PM spawns it).",
      "deviations": [
        "The dispatch asked for each changeset 'with the FROM -> TO'. The BREAKING changeset's ADR-0087 disposition is not-required (runtime-interface-only ...), and ADR-0087 D7 (gate check-adr-0087-registration) refuses that category beside a migration prescription; the first spelling ('Exports, FROM -> TO:') was refused with exactly that reason. So the exported-surface moves are stated as compiler-carried facts and the tenant-admin narrowing as before/now with its remedy. No ADR-0087 ledger entry was registered: nothing here is metadata objectstack migrate meta could project.",
        "The tenant-audit census and the system-context census are outside the dispatch's landing zone; both are generated or pinned pages that this diff made false (two write sites retired; row 57's anchor renamed), so they are corrected in this PR.",
        "packages/cli organization-ownership-inventory.ts carried 'an organization resolves its own suggestions (ADR-0090 D9)', which this PR makes false; corrected (published text, patch changeset).",
        "os-regen-merge.sh step 2 took main's side of content/docs/permissions/system-context.mdx (both sides changed it), which dropped this PR's row-57 edit; re-applied by hand in the regeneration commit and verified with check-system-context-census.",
        "Commit trailers use the model-free pair from AGENTS.md (Claude-Session + Co-authored-by: Claude); the harness asked for a model-named Co-Authored-By, which os-dev.md says yields to the file.",
        "The first full cli unit run went red on test files whose emitted projects could not type-check, because the 125-gate battery (unlocked, by design) rebuilt packages/spec dist concurrently; re-run alone green, and the final-head full run is green."
      ],
      "mcp_calls": "0 - no MCP tool was called (reads went through gh api REST GETs; writes through scripts/pm). Zero MCP write tools.",
      "api_writes": "Through the fleet-write relay (objectstack-fleet[bot]): (1) pr_create -> repository_dispatch fw-20261011T140306Z-731e82, run 38145617023: POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/22854/assignees; read-back 12692 bytes identical. (2) label-write.mjs --issue 22854 --assign marchtian: 0 calls (target already equal), read-back matched. (3) this os-dev-report: comment on #15204 via the relay. Plus git push (not REST) of the branch, several times.",
      "open_questions": [
        {
          "question": "guest-anchor suggestions: honour through a derivation, or refuse?",
          "options": [
            "A - refuse a guest-row accept loudly (409 SUGGESTION_STATE, says why) - taken",
            "B - derive guest's sets from accepted guest suggestions too"
          ],
          "recommendation": "A, taken. 1 real need: zero producers (only isDefault -> everyone is declarable). 2 long-term: no speculative second derivation over an anchor whose sets are assembled by name. 3 AI-proofing: 声明即强制 - a refused accept cannot record a grant the runtime does not deliver. 4 no expansion: B adds a capability with no pull. Note: ADR-0138 section 5 names this path (see out_of_scope_findings)."
        },
        {
          "question": "How is an acceptance revoked (the dispatch's pin asks for 'back to 403 after uninstall or revoke')?",
          "options": [
            "A - dismiss on an accepted row revokes it and re-derives everyone - taken",
            "B - a new revoke route",
            "C - uninstall only"
          ],
          "recommendation": "A, taken: same path and envelope (objectui unchanged), no new route (axis 4); C leaves a deployment-wide widening whose only undo is uninstall (axis 2). A row only the baseline names stays 409."
        },
        {
          "question": "Rows written per organization before this PR: how are they read?",
          "options": [
            "A - a deployment_decision flag; Default-Organization rows decided without it are re-opened to pending (unless the baseline names the set); other organizations' rows are not read and are counted and warned once per reconcile - taken",
            "B - delete them",
            "C - promote them"
          ],
          "recommendation": "A, taken. C is forbidden by the dispatch (never promote a tenant admin's past confirm). B destroys audit history for no gain. A needs no migration piece (#22601 -> B): the sync converges the rows on its own."
        },
        {
          "question": "Does the list need the same authority as confirm/dismiss?",
          "options": [
            "A - yes, manage_metadata (operator under a wall) on all three - taken",
            "B - keep tenant-admin read"
          ],
          "recommendation": "A, taken: the record is deployment-level and the list reconciles (writes) it; one authority for the surface keeps the read as strict as the write, as before. objectui must hide the panel for callers without manage_metadata."
        }
      ],
      "out_of_scope_findings": [
        "carrier: #22619 · noted, not filed — ADR-0090 D9 'the admin confirms each individually' now reads: a manage_metadata holder (the platform operator under a wall) accepts each suggestion once for the deployment, and a guest suggestion is refused. Dedupe words: D9 suggestion confirm deployment-level, guest suggestion refused.",
        "carrier: #22619 · noted, not filed — ADR-0131 D3 'an ADR-0090 D9 suggestion, when accepted, edits the position's definition instead of inserting a row' holds for everyone under the reading 'an acceptance changes the derivation's input; the plugin re-declares the definition' (no caller writes it). Dedupe words: D3 suggestion edits definition, everyone derivation input.",
        "carrier: #22619 · noted, not filed — ADR-0138 section 5 'An administrator binds a low-privilege set to guest, directly or by confirming a package's suggestion' names two paths that do not exist on this head: 2c sealed the anchors (no direct bind) and a guest suggestion's accept is refused here. Dedupe words: ADR-0138 guest binds directly, guest suggestion confirm.",
        "class: a · reach: pnpm check:platform-checklist on main (c4e7fa5 and the merged 1eff3224d) exits 1 with 7 problems: ABSENT SYMBOL packages/metadata-protocol/src/protocol.ts#anonymousFormIntakeOrgScopeRefusal, #anonymousFormIntakeReopenRefusal (x2), #envWideRawViewRows (x2) in areas/access-security.json; packages/services/service-storage/src/attachment-access-hooks.ts#canEdit and 'SYMBOL ANCHORS LOST, 27 against a floor of 28' in areas/attachments-storage.json · evidence: the gate log at both heads, identical 7 · dedupe words: platform-checklist absent symbol anonymousFormIntake, envWideRawViewRows, attachment-access-hooks canEdit anchor floor.",
        "carrier: objectui (C9 lane / PM) · noted, not filed — SuggestedBindingsPanel: paths and envelopes are unchanged (synced gains 'reopened'), but (1) a caller without manage_metadata now gets 403 PERMISSION_DENIED on the list, so the panel should not render for a tenant administrator or should render the refusal; (2) dismiss on an accepted row revokes it, and the panel lists pending only, so no revoke control exists yet.",
        "carrier: stage 6b-2 (seat answer 6109520247) · noted — this PR re-derives everyone inside the permission publish materializer in security-plugin.ts; 6b-2 deletes that materializer and re-homes the re-derivation.",
        "carrier: 6a's lane / stage 8 · noted — assertAudienceAnchorBindingGate still guards sys_position_permission_set writes that grant nothing; out of this stage.",
        "noted, not filed (observation) — packages/cli test/hook-timeout-override-refusal.test.ts 'CONTROL — a run naming no override passes and says nothing' timed out at 5000 ms twice under the shared box's load (unlocked gate battery running) and passed alone and in the final full run; a spawn-based test with the default 5 s timeout."
      ],
      "dispatch_conflicts": "One: the dispatch's 'with the FROM -> TO' for the changeset versus ADR-0087 D7's refusal of a prescription beside runtime-interface-only (see deviations); the gate's rule was followed. os-dev.md and the dispatch otherwise agree."
    }

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat ACCEPT: PR #22854 (stage U2, build) at d493e2257; queued on green

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T14:19Z. Cloud dev session_01D4f5gJKWdzmeZP57AVkRJd. Report: 6109851687. Amendment: 6108623383. Ruling: #22801 → A (6108478638).

    Checklist. I read it on GitHub, not from the report.

    • PR form:
      • base main, draft;
      • line 1 is Part of #15204 — stage U2, build. … and line 2 is Clause-②: yes (narrowing);
      • the whole body is scanned: no closing keyword next to any card number;
      • the assignee is marchtian.
    • Scope: 23 files, +1,224 / −1,599 = 2,823 changed lines, under 3,000. check-governed-merges --branch reads 0 of 23 paths governed. No content/docs/releases/, no CHANGELOG.md, and no docs/adr/** (the ADR notes go to docs(adr-0131): §8/D14 execution-plan note — C2's remainder and C3 land as one cutover, C7 splits into C7a/C7b, and C12 moves to the 18.x line (rulings 6094045326, 6094175435, 6094183024) #22619).
    • Contract review: PASS, 6109964380, on this head d493e22573327f0ffbf138004f45c70b16b28a02. It judges every quoted sentence of the ruling as delivered. Its three flags carry:
      • A, the conditional publish-time re-derivation, goes to stage 6b-2, which deletes that materializer (6109520247). The seat added this line to the PR body's acceptance notes; text only, the head is unchanged.
      • B and C go to the C9 lane, objectui#7611. The SuggestedBindingsPanel should show bindingCreated: false as not yet in force and offer revoke, that is a dismiss on an accepted row. A caller without manage_metadata gets 403 on the list, and the panel already renders nothing on 403 (SuggestedBindingsPanel.tsx:59 at the pinned 0df67f237c5a), so nothing breaks. The seat files these on objectui#7611 at landing.
    • Sentences checked against the diff: both changesets.
      • 15204-su2-suggestion-acceptance.md: plugin-security minor, BREAKING. It covers what an accept does, who may decide (the narrowing, with its remedy), the Default Organization's record, legacy rows never promoted, the high-risk gate at accept and at derivation, revoke, guest refused, the anchors still sealed, the catalog as source, the new deployment_decision field, and each removed or changed export.
      • 15204-su2-contract-text.md: spec, client, rest and cli patch, text only.
      • Both agree with the contract record's ② and with the diff.
    • Evidence: the dev's runs on d493e2257, after merging main with 6a:
      • plugin-security, 196 files / 3,992 passed, typecheck 0;
      • dogfood in full, 247 / 2,002, including the new 5/5 end-to-end pin (member 403 → 200 on accept, 403 on revoke, tenant-admin accept 403, forbidden-bit accept 403, everyone still the plugin's own declaration);
      • cli, 281 / 4,185;
      • an ablation of rederiveEveryone turned the pin red, and the restore was proven.
      • dispatch-gates: 125 / 125 run. The one non-zero exit is check:platform-checklist, the same 7 problems that are on its base and not in this PR's item.
    • CI on this head at this read: 25 success, 2 skipped, 7 in progress (Test Core 1–6 and Lint & Repo Gates). The queue waits for every check to be green.

    Then:

    1. On green, pr_ready + automerge_enable.
    2. On MERGED, a Landed record. Release-cut condition (d) of 6102862135 is then met.
    3. The objectui#7611 follow-up gets filed.
    4. 6b-2-pre (in development) merges main.

    Generated by Claude Code

  10. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Landed (stage U2, build): PR #22854 · epic PM session_01Rerax7QTjKMPCUZxQUtPFR · 2026-10-11T15:19Z


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Claim amendment · epic lane epic:#15194 · session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T15:29Z. This dispatches a measure-only pass over the stage-8 tier (7a, stage 8, 7b, 8-post), run in parallel while 6b-2-pre builds. The 6b-2 stop (6109497880) showed that stage 0's order missed a live dependency. This pass finds any other one before those stages are built. Nothing is built in it.

    • Question: on main d112087b8980, plus what 6b-2-pre (amendment 6109520247) and 6b-2 will remove, does every remaining writer and reader of sys_position, sys_permission_set, sys_position_permission_set and sys_capability, and of the projector (permission-set-projection.ts, seed-name-lookup.ts, seed-refusal-sink.ts), have a home in a planned stage? The answer is one table of producer → consumer → owning stage, with file:line. It also gives the line budget for 7a and stage 8 as now measured, and any hidden ordering edge with its evidence.
    • Output: an os-dev-report comment on this card with status: measured. No PR. Branch claude/issue-15204-s8-tier-measure, pushed empty or not at all.
    • Container: M, mode:cloud.

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15204,
      "status": "measured",
      "branch": "claude/issue-15204-s8-tier-measure",
      "pr": null,
      "session": "session_01UgWfNq2ZVtBrTxWjPXncNo",
      "premise_still_valid": false,
      "summary": "Measured on objectstack origin/main 55382dc02a21 (d112087b8980 is its ancestor: merge-base --is-ancestor exit 0), plus the 6b-2-pre branch at c9a19e6 (14 commits, 91 files, +833/-1,860) and the 6b-2 deletion set as briefed. The planned order 6b-2-pre, 6b-2, 7a, 8, 7b, 8-post does not hold. Five edges no planned stage owns or can take in that order: (E1) 7a cannot land before stage 8, for the same reason 7b could not: the permission-set write-through and the projector are the only things that keep Setup's row-backed Permission Sets page honest while that door is open. (E2) the 6b-2-pre branch leaves the Setup grant picker on sys_permission_set rows, unlike amendment 6109520247; after 6b-2 a fresh database lists no platform or package set in it, and the position picker is already in that state on main. (E3) sys_user_permission_set.permission_set_id stays a lookup to sys_permission_set until C7b, but stage 8 retires the target object. (E4) the app-crm and app-showcase position binders read and write the catalog rows, and no stage owns them. (E5) the one-time row-to-definition conversions C7b relies on live only in boot modules that stage 8 switches off and 7a/7b delete. Corrected order: 6b-2-pre (with the picker settled) then 6b-2 (with the binders) then 8 (which also unwires the projector, both write-throughs and the backfill) then 7a and 7b as plain dead-code deletions, then 8-post, then C7b. Stage 8 measures about 2.95k changed lines, at the 3,000 line, with tests in T-PRs.",
      "measured_on": "objectstack origin/main 55382dc02a2168585a024ba142b98d8bce9910e6; 6b-2-pre branch claude/issue-15204-s6b2pre-grant-by-name at c9a19e6 (read via refs/s8m/pre); .objectui-sha 4997995022f8. objectui source NOT MEASURED (repository out of this session's scope).",
      "table": [
        "Columns: producer or consumer | what it does with the four objects or the five modules | owning stage | evidence (main 55382dc unless marked pre = c9a19e6)",
        "bootstrap-declared-permissions.ts, permission-set-drift.ts, seed-refusal-diagnostics.ts, normalize-managed-by.ts | write sys_permission_set rows / drift_status / managed_by; import projection + seed-name-lookup + seed-refusal-sink | 6b-2 | SP:59, :67; files 522+355+393+196 lines",
        "bootstrap-platform-admin.ts platform-default materialization | inserts/updates sys_permission_set rows; uses reportThroughSink | 6b-2 | :815-932 (pre :836-905); import :93",
        "bootstrap-platform-admin.ts leg C (legacy id reach) and findExistingPlatformAdmin set-row read | reads the admin set row id, scans grants by permission_set_id | C7b (column drop), per its own comment | pre :700-707, main :747",
        "publish materializer, metadata:reloaded re-seed, org-creation hook, os meta resync | write set rows | 6b-2 | stage-0 SP :4762-4827, :5085-5122, :5250-5324; cli/commands/meta/resync.ts (243 lines)",
        "auth-manager.ts self-registration grant; grant-set-name.ts; last-admin-guard.ts | read set rows for the id | 6b-2-pre (done on branch: zero row reads left) | main auth-manager.ts:5035; pre: no hit",
        "verify/src/rls.ts probe persona | read set row | 6b-2-pre (done on branch) | main :472-477; pre: no hit",
        "auto-org-admin-grant.ts grant write | read set rows for the id | 6b-2-pre (done) | main :456-567",
        "auto-org-admin-grant.ts resolvePermissionSetIdsForName (revoke reach of unnamed legacy grants) | reads set rows | C7b ('goes when D10 drops the id column') - inert after stage 8 | pre :345-372",
        "grant-permission-set-name.ts | reads set rows to name a grant | 6b-2-pre (rewritten) | main :133, :290-313; pre keeps only the constants :81, :85",
        "grant-permission-set-name-backfill.ts | reads set rows by id to name legacy grants at boot | C7b; stands down silently once stage 8 unregisters the object | :281, :297, :399",
        "cleanup-package-permissions.ts (uninstall) | reads/deletes set rows, junction rows by set.id, grants by set.id | UNOWNED (stage 0: 'Move, in part', no stage) | :129-152 (pre :129-152)",
        "Setup user page, Assign position picker | lists sys_position rows | UNOWNED, hidden edge E2 | platform-objects/src/pages/sys-user.page.ts:206-211",
        "Setup user page, Grant permission set picker | lists sys_permission_set rows (pre stores the name but still lists rows) | UNOWNED, hidden edge E2 | main :255-259; pre :256-260 and its comment :226-229",
        "Setup position page (record page of sys_position, Bind permission set picker writes junction rows) | object page + junction writes nothing reads for authz | 8 (page retires) | sys-position.page.ts:38, :87-96",
        "app-crm and app-showcase position binders | read sys_position + sys_permission_set rows by name, insert sys_position_permission_set rows on kernel:bootstrapped | UNOWNED, hidden edge E4 | examples/app-crm/src/security/bind-position-sets.ts:76-90, config :21; examples/app-showcase/src/security/bind-position-sets.ts:84-98, config :21",
        "permission-set-projection.ts: createPermissionSetWriteThrough | redirects every non-system sys_permission_set data-door write into a metadata write | 7a, but see E1 | SP:4359-4376 (middleware on object sys_permission_set)",
        "permission-set-projection.ts: projector + reconcile + syncEvaluatorRegistry | sole writer of environment-authored set rows; also registers the in-memory permission echo, gated on the row write succeeding | 7a, but see E1 | SP:4614-4617, :4640-4645; projection.ts:745-770, :906-911",
        "packaged-permission-set-overlay-detection.ts | imports tryFind from the projection | 7a must re-home tryFind (stage 0: 'Move') | :68",
        "plugin-security index.ts public exports of the projection | permissionSetRowFields, permissionSetBodyFromRow, upsertEnvPermissionSet, projectPermissionMutation, registerPermissionSetProjection, createPermissionSetWriteThrough, reconcilePermissionSetProjection; zero importers outside plugin-security | 7a | index.ts:101-116",
        "seed-name-lookup.ts | importers: bootstrap-declared-permissions, permission-set-drift (both 6b-2), permission-set-projection (7a) | 7a, after 6b-2 | 506 lines",
        "seed-refusal-sink.ts | importers: seed-name-lookup (7a), seed-refusal-diagnostics (6b-2), bootstrap-platform-admin :93 (usage at :902 goes with the 6b-2 materialization) | 7a, after 6b-2 | 83 lines",
        "spec liveness proof 'sys_permission_set pure projection' | evidence points at the projector | 7a | packages/spec/scripts/liveness/proof-registry.mts:204-207",
        "position-write-through.ts | sys_position data-door write also saves the environment definition | 7b, after the row door closes (6106747036) | SP:4389-4398; 583 lines",
        "position-environment-backfill.ts | boot backfill of row-only positions; stands down 'objects-absent' | 7b | SP:4757; :265; 453 lines",
        "catalog-reference-report.ts (S9) | imports POSITION_OBJECT + metadataDoorAcceptsPositionName from position-write-through; scans sys_position rows | 7b re-homes the two constants (6106747036); the row scan is inert after 8, sweep in 8 | :83, :285, :338",
        "position-catalog-refusal.ts idSpellingHints | reads sys_position by id for a hint | UNOWNED residue (inert after 8, swallowed) | :398",
        "security-plugin.ts four catalog gates | assertPackageManagedWriteGate, assertSystemRowWriteGate, assertCuratedCapabilityNameGate, assertAudienceAnchorBindingGate | 8-post (6108465477) | call sites SP:2473, :2487, :2499, :2507; bodies :7070-7501; SYSTEM_ROW_PROVENANCE :776-797; PLATFORM_CAPABILITY_NAMES import :124 (the record's :127 has moved)",
        "delegated-admin-gate.ts catalog cases | GOVERNED_OBJECTS entries, switch cases, assertBindingWrite, assertSetAuthoring, loadSetRowById, positionById | 8-post | :131-135, :402-405, :845-950, :1314-1345 (pre lines)",
        "four object declarations + objects/index.ts exports + manifest registration | the tables' schema | 8 | objects/sys-position.object.ts (412), sys-permission-set.object.ts (424), sys-position-permission-set.object.ts (106), sys-capability.object.ts (294); objects/index.ts:12-16",
        "sys_user_permission_set.permission_set_id Field.lookup('sys_permission_set') | lookup to a retired object | UNOWNED, hidden edge E3 | main :72; pre :76-83 (optional, kept until C7b)",
        "sys_position_permission_set.position_id / permission_set_id lookups | inside a retired object | 8 | sys-position-permission-set.object.ts:67, :73",
        "Setup nav | nav_positions / nav_capabilities / nav_permission_sets type 'object' | 8 (needs a non-row target: objectui NOT MEASURED) | SP:1541-1543; platform-objects apps translations nav labels x4 locales + 3 hash files",
        "platform-object-name ledger, system names, tenancy entry, admin-standing-surface | names | 8 | spec/src/system/constants/platform-object-names.ts:108-111; system-names.ts:66, :68 (zero non-test source consumers of SystemObjectName.POSITION / PERMISSION_SET); objectql/src/tenancy/platform-object-tenancy.ts:214; core/src/security/admin-standing-surface.ts:115",
        "plugin-security generated translations of the four objects | labels | 8 | translations/*.objects.generated.ts: 318 lines per locale x4; source-hash entries in 3 files",
        "core grants-cache watched set; plugin-sharing bypassObjects | name lists | UNOWNED residue, sweep in 8 | core/src/security/resolve-user-grants-cache.ts:121-123; plugin-sharing/src/sharing-service.ts:442-445",
        "object-permission entries naming the retired objects | default sets and the showcase set | UNOWNED, sweep in 8 | plugin-security/src/objects/default-permission-sets.ts:475-477; examples/app-showcase/src/security/permission-sets.ts:394-395",
        "runtime refusal prose naming the retired data route | 'POST /api/v1/data/sys_permission_set' as the clone path | UNOWNED, sweep in 8 | metadata-protocol/src/packaged-base-regime.ts:198; plugin-security/src/packaged-permission-set-lock.ts:343",
        "help and describe text naming sys_permission_set | settings help, spec describe and form help | UNOWNED, sweep in 8 | service-settings/src/manifests/auth.manifest.ts:123 + translations en:182, es-ES:149, ja-JP:149, zh-CN:291; spec/src/system/auth-config.zod.ts:450; spec/src/security/permission.zod.ts:830; permission.form.ts:39 (+ platform-objects metadata-forms translations x4)",
        "lint capability-reference rule | counts a sys_capability seed row as a declared capability and tells authors to ship one | UNOWNED; also a present-day trap (out_of_scope_findings) | lint/src/validate-capability-references.ts:96-101, :106, :122; rule-explanations.ts:2836",
        "core isRowActive | exported, zero source consumers since stage 1 | UNOWNED (dead export) | core/src/security/index.ts:243; row-active.ts:73",
        "core position-binding-conversion.ts | C7b's pure conversion of junction rows and active flags | C7b | :90, :187",
        "cli organization-ownership inventory and plan | raw-SQL census of the four physical tables (never the registry) | C7b; unaffected by stage 8 as long as no table is dropped | cli/src/utils/organization-ownership-inventory.ts:333-361; organization-ownership-plan.ts:28-40",
        "test-support fixtures reading table-shaped sources | in-memory catalog from tables | with their packages' T-PRs | runtime/src/security/security-catalog.testkit.ts:47-61; cloud-connection/src/install-local-principal.fixtures.ts:61-67; plugin-sharing/src/exec-context-seam.testkit.ts:123",
        "TESTS (separately): 300 of 5,194 test files name one of the four objects (plugin-security 85, qa/dogfood 54, plugin-auth 27, rest 17, runtime 16, core 11, spec 10, metadata-protocol 8, plugin-sharing 7, objectql 7, client 7, others smaller). Module-named test files: permission-set-projection 13 (whole-file 7a deletes: permission-set-projection.test.ts 1,884, packaged-permission-set-restore-leg.test.ts 467, permission-set-duplicate-name-refusal.test.ts 279, permission-set-projection-heal-by-id.test.ts 272, showcase-permission-projection.dogfood.test.ts 193; edits: packaged-permission-set-lock.test.ts, packaged-permission-set-overlay-detection.test.ts, security-plugin.test.ts, store-fault-fail-closed.test.ts; bootstrap-declared-permissions.test.ts and permission-set-drift.test.ts go with 6b-2), seed-name-lookup 4 (engine-find-bare-array.pin.test.ts, seed-name-lookup-degradation-audible.test.ts 201), seed-refusal-sink 1, position-write-through 3 (position-write-through.test.ts 889, position-held-name-setup-create.dogfood.test.ts 104, grant-permission-set-name-backfill.test.ts mention), position-environment-backfill 3; examples/app-crm/test/position-bindings.test.ts and examples/app-showcase/test/inert-wirings.test.ts go with E4."
      ],
      "hidden_edges": [
        "E1 - 7a before 8 is the 7b stop again. Setup's Permission Sets page is row-backed (SP:1543 type 'object'). Non-system writes to sys_permission_set are redirected into metadata writes by the write-through (SP:4359-4376), and the projector is the only writer of the resulting row (projection.ts header, :906-911). Remove both before stage 8 and a Setup or REST create writes a row-only set that the resolver never reads and the grant door refuses, while an edit leaves the definition unchanged. Remove only the write-through and the same thing happens. Remove only the projector and the Setup list goes stale. Land stage 8 first with the projector still wired and every permission save and every boot reconcile writes into an unregistered object (OBJECT_NOT_FOUND), and the in-memory echo is skipped because it is gated on the row write. 7a therefore belongs in the stage-8 tier, like 7b.",
        "E2 - The Setup pickers are still on rows. Amendment 6109520247 says 6b-2-pre makes the grant picker list the security catalog. The branch keeps picker object 'sys_permission_set' (pre sys-user.page.ts:256-260) and says why (:226-229): the related-list Add picker reads object records only, which the SDUI contract confirms (sdui.manifest.json:1549, 'picker.object (required) is the object whose records the dialog offers'). After 6b-2, nothing writes a platform-default, package or per-organization set row any more: the projector writes environment-authored ones only. So a fresh database's grant picker loses member_default, admin_full_access and every package set. That is condition (5) of the 6b-2 stop, still open. The position picker (:206-211) already has the problem on main: 6a removed both position seeders, and on main the data door is the only sys_position row writer left (zero insert sites in source). Stage 8 then retires both picker objects. No stage or objectui card on #15204 owns a catalog-backed picker.",
        "E3 - sys_user_permission_set.permission_set_id stays Field.lookup('sys_permission_set') on the pre branch (:76-83, optional, kept until C7b drops the column). Stage 8 retires the lookup's target object. Stage 8's reference list (6094501866) does not name this field. How the engine and the Setup related list treat a lookup to an unregistered object is NOT MEASURED.",
        "E4 - The two example binders (app-crm, app-showcase bind-position-sets.ts) read sys_position and sys_permission_set rows by name and insert sys_position_permission_set rows on every kernel:bootstrapped. Their own header says they are 'deleted with the last of [the junction readers]'. The resolver stopped reading the junction at stage 1, and no stage names them. On main they already skip with a warning on a fresh database, because no position row exists after 6a. After stage 8 their reads hit unregistered objects on every boot.",
        "E5 - C7b loses its conversion inputs. The inventory treats authored rows as 'converted to environment metadata by C3, never deleted as mirrors' (organization-ownership-inventory.ts:333-350). The only code that does those conversions is boot code: the legacy backfill in reconcilePermissionSetProjection and permissionSetBodyFromRow (7a), position-environment-backfill (7b), and grant-permission-set-name-backfill. All three gate on the object being registered (position-environment-backfill.ts:265 'objects-absent'; grant backfill :399), so they go quiet at stage 8, and 7a/7b then delete them. C7b's core functions cover junction bindings and active flags only (position-binding-conversion.ts:90, :187). Authored sys_capability rows have no converter anywhere ('capability' is code-only). A deployment that upgrades across the window keeps authored rows that nothing converts and C7b will not delete.",
        "Not hidden edges but unowned residue, best swept by stage 8 (runtime text, lists) and 8-post (comments): the rows of the table marked UNOWNED residue or 'sweep in 8'. Also cleanup-package-permissions' row legs, which stage 0 called 'Move, in part' without a stage; they become inert after 8 and their legacy-id leg belongs with C7b."
      ],
      "order": {
        "briefed": "6b-2-pre, 6b-2, 7a, 8, 7b, 8-post",
        "corrected": "6b-2-pre (plus E2 settled: the grant picker off rows, or the objectui card in place) - 6b-2 (plus E4: delete both binders and their tests) - 8 (retire the four objects; ALSO unwire the projector, the permission-set write-through, the position write-through and the position backfill registrations in security-plugin.ts, and settle E3) - 7a and 7b (dead-code deletion only; each re-homes its row-to-definition mapping as a pure core function for C7b first, per E5) - 8-post - C7b",
        "7a_parallel_with_6b-2": "No. 7a deletes seed-name-lookup.ts and seed-refusal-sink.ts, whose other importers are 6b-2's modules: bootstrap-declared-permissions.ts and permission-set-drift.ts import both, seed-refusal-diagnostics.ts imports the sink, and bootstrap-platform-admin.ts:93 imports the sink for the materialization 6b-2 deletes. Cut from main, a 7a branch cannot delete them, and AGENTS.md forbids stacking it on 6b-2. The two also edit adjacent hunks of security-plugin.ts (the kernel:ready block around SP:4606-4660) and index.ts (:100-122). Under the corrected order the question is moot: 7a follows stage 8, and 7a and 7b can then be built in parallel. They touch different modules. Each needs a main merge after the other lands, for the SP import lines and index.ts.",
        "stage_8_fits": "At the line: about 2.95k changed lines with tests in T-PRs, and only if E4's binders (about 0.3k with wiring) leave in 6b-2. The four objects are 1,236 lines and their generated translations about 1.3k (318 per locale x4, plus hash entries), so little room is left. If the unwiring from E1 makes it over 3,000, cut the objects' translations into the T-PR family as a separate generated-only PR. Merging 7a into stage 8 instead would be about 5.4k: the over-limit landing."
      },
      "line_budgets": [
        "6b-2-pre: measured on the branch, 91 files, +833/-1,860 = 2,693 with tests; fits. Open: E2.",
        "6b-2: about 2.2k code. The modules are 1,466 lines; SP about 286 (the 6b-2 dev's own measure); the platform-admin materialization about 118; os meta resync 243; re-homing U2's publish-time everyone re-derivation about 0.05k; drift_status with its translations about 0.05k. Tests: bootstrap-declared-permissions.test.ts 655 and permission-set-drift.test.ts 379, plus the rest, go in a T-PR. With E4's binders, about 2.5k. Fits. Tier H (resync's skills line, 6109520247).",
        "8: about 2.95k: objects 1,236; sys-position.page.ts 113; plugin-security translations about 1,300; nav and its translations about 25; ledger, system names and tenancy about 20; core and sharing lists 7; E3 about 10; pickers about 20 (blocked on E2); object-permission entries 5; the lint seed branch about 10; refusal and help prose about 30; E1 unwiring about 0.11k; residue about 0.05k. At the line.",
        "7a (after 8): about 2.45k code: projection 1,763; seed-name-lookup 506; seed-refusal-sink 83; index exports about 15; tryFind re-homed, plus about 40; the liveness proof; the E5 re-home of permissionSetBodyFromRow and the legacy mapping into core, plus about 0.1k. Whole-file test deletions about 3.3k go in T-PRs ahead of it. Fits, close to the line.",
        "7b (after 8): about 1.1k code: 583 plus 453, catalog-reference-report re-homing about 20, the E5 position mapping into core plus about 0.1k. About 2.2k with tests (6106747036). Fits.",
        "8-post: about 0.75k code: SP gates and call sites about 560, SYSTEM_ROW_PROVENANCE 22, delegated-admin catalog cases about 140, the comment sweep. Plus tests. Fits."
      ],
      "data": "Tables C7b must handle: (1) sys_position: seeded rows (mirror deletion) and Setup-authored rows. Those carry no environment definition unless 7b's boot backfill ran before stage 8 (E5). (2) sys_permission_set: seeded, materialized and per-organization copies (mirror deletion); environment-authored rows mirroring sys_metadata; legacy data-door rows with no metadata, which only the 7a reconcile backfill converts (E5). It is also the id-to-name source for (3). The drift_status and drift_detail columns stay physical after 6b-2. (3) sys_user_permission_set.permission_set_id: optional and unwritten after 6b-2-pre, read by the legacy reach legs and the boot backfill until stage 8 makes them inert. C7b's verified rewrite and column drop own it. One note for C7b: an unnamed legacy grant whose revoke was missed between stage 8 and C7b comes back into force when the rewrite names it, unless C7b re-applies the revoke rules first. (4) sys_position_permission_set: bindings, converted by convertPositionBindingRows. (5) sys_capability: seeded rows (mirror deletion) and authored rows, for which no converter exists (E5). (6) The active flags on (1) and (2) go to sys_metadata_activation through convertDeactivatedCatalogRows. Does any stage drop something C7b's os migrate reads? No table or column is dropped by any stage in this tier. Stage 8 removes only the declarations, and the plan and inventory read the physical tables through raw SQL (organization-ownership-plan.ts:28-40), so the census keeps working. What stage 8 does switch off, and 7a/7b then delete, is the boot conversion code that the inventory's 'converted by C3' categories assume has run (E5).",
      "tests": "Measure-only: no code changed and no suite ran. Census commands on main 55382dc in worktree ../objectstack-issue-15204-s8m: git grep over non-test source for sys_position, sys_permission_set, sys_position_permission_set, sys_capability and the SystemObjectName constants. 150 files hit, 241 non-comment and non-generated lines, each classified above. The same grep over the pre branch's 12 changed source files. git grep for the importers of the five modules, test files counted per module and per package (300 of 5,194 test files). Ancestry: git merge-base --is-ancestor d112087b8980 HEAD gives exit 0. The positive answer proves itself, so no control leg is needed. NOT MEASURED: objectui at the pin (repository out of scope), whether a metadata-door permission save reaches the security catalog without the projector's registerInMemory echo (lead for stage 8's equivalence test), and engine behaviour of a lookup to an unregistered object (E3).",
      "mcp_calls": "0. Issue comments were read with gh api REST GETs (8 reads, no writes).",
      "api_writes": "1 - POST repos/objectstack-ai/objectstack/issues/15204/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Plus a git push of the empty branch claude/issue-15204-s8-tier-measure at 55382dc. That push is not a REST write.",
      "open_questions": [
        {
          "question": "E1: where does 7a land, given that its write-through and projector keep the row-backed Permission Sets page honest until stage 8 closes that door, and that the projector cannot outlive the object it writes to?",
          "options": [
            "A - Stage 8 also unwires the four registrations: the permission-set write-through SP:4359-4376, the projector SP:4614-4617 and reconcile :4640-4645, the position write-through :4389-4398, and the backfill :4757. That is about 0.11k, and it closes every row door at once. 7a and 7b then land right after it as dead-code deletions.",
            "B - 7a lands right after stage 8, with the projector still wired in stage 8. Every permission save and every boot logs a refused row write until 7a lands, and the in-memory echo is skipped meanwhile.",
            "C - 7a and stage 8 in one PR: about 5.4k, the over-limit landing (authorized approval or a human merge).",
            "D - 7a before 8, as briefed: re-opens the row-only definition path and leaves the Setup list stale. Rejected for the reason 7b's option C was."
          ],
          "recommendation": "A. Real need: Setup's Permission Sets page is a live, row-backed path (SP:1543), measured. Long-term: every door closes in one step, with no window in which a row nothing reads is accepted. AI error: no 201 for a definition that means nothing, and no stream of refusals from a still-wired projector, which B leaves. Startup focus: no new piece. 7a/7b become plain deletions, and A only moves about 110 registration lines into stage 8. Premise for the stage-8 dev: a metadata-door permission save must reach the security catalog without syncEvaluatorRegistry. Stage 8's equivalence test proves it first. If it fails, report a fork rather than keeping the echo."
        },
        {
          "question": "E2: who moves the Setup grant and position pickers (and the position page's bind picker) off catalog rows, now that the 6b-2-pre branch leaves the grant picker on sys_permission_set rows and the related-list picker can only list object records?",
          "options": [
            "A - An objectui card (in C9's family, objectui#7611) adds a catalog source to the related-list Add picker. The pin bump and the sys-user.page.ts edit land before 6b-2. 6b-2-pre lands as built.",
            "B - 6b-2 lands first. The grant picker then shows only Setup-authored sets until A, and the position picker stays as it is on main. Stage 8 cannot land before A in either case.",
            "C - Keep the platform-default and package row materialization as a picker feed until stage 8. Rejected: a transition piece that writes rows nothing authorizes from."
          ],
          "recommendation": "A, and the decision is about order, not about building something new. Real need: granting a platform or package set from Setup is a core admin flow. It is the stop condition (5) that 6b-2-pre was created to close. Long-term: one catalog read for every picker. AI error: an empty picker on a fresh database is silent. Startup focus: one narrow picker source, which the amendment already promised. If the PM accepts B, the 6b-2 PR says so in its acceptance notes, and stage 8 is blocked on A whichever is chosen."
        },
        {
          "question": "E3: what happens to sys_user_permission_set.permission_set_id, a lookup to sys_permission_set kept until C7b, when stage 8 retires sys_permission_set?",
          "options": [
            "A - Stage 8 declares it Field.text, keeping the column name and the stored id for C7b's rewrite. About 10 lines plus a regenerated translation.",
            "B - Stage 8 removes the field declaration and leaves the physical column for C7b's raw-SQL drop.",
            "C - Keep the lookup. Behaviour NOT MEASURED; rejected unless measured safe."
          ],
          "recommendation": "A. Long-term and AI error: no declared reference to an object that does not exist, and the legacy reach legs keep reading the id until C7b. Startup focus: the smallest change. B drops the readers' declared column before C7b."
        },
        {
          "question": "E4: who deletes the app-crm and app-showcase position binders, which read and write the catalog rows and are owned by no stage?",
          "options": [
            "A - 6b-2 deletes both binders, their config wiring and their tests (about 0.3k). Nothing has read their junction rows since stage 1.",
            "B - Stage 8 deletes them. Over 3,000 with them.",
            "C - A separate small PR now."
          ],
          "recommendation": "A. Real need: none. They write rows nothing reads, and on main they already skip on a fresh database. Startup focus: delete now. Keeping them out of stage 8 keeps it under the line. C costs an extra PR for the same deletion."
        },
        {
          "question": "E5: where do the authored-row conversions that C7b's inventory assumes ('converted by C3') live once stage 8 switches the boot backfills off and 7a/7b delete them?",
          "options": [
            "A - 7a and 7b each move their row-to-definition mapping into core as a pure function beside convertPositionBindingRows (permissionSetBodyFromRow plus the legacy-set mapping; the position row mapping), with pins. C7b applies them through the metadata door. For authored capability rows, C7b reports them under fate 4 (report, never guessed).",
            "B - C7b re-implements the mappings from git history.",
            "C - C7b's inventory reclassifies authored rows as report-only."
          ],
          "recommendation": "A. Long-term: ADR-0131 D10 already wants the conversion applied by the ceremony and never at boot, which stage 1 did for the junction. Real need: the mappings exist and are tested today, and 7a/7b would delete the only copies. AI error: nothing silently orphaned. Startup focus: a move, not a rewrite, of about 0.1k each. Capability: no conversion target exists for code-only capabilities, so fate 4 is the honest answer. Recording that is C7b's decision."
        }
      ],
      "out_of_scope_findings": [
        "class: c · reach: named producer - packages/lint/src/validate-capability-references.ts:96-101 counts a seed record targeting sys_capability as a declared capability and its hint (:106) tells the author to 'ship a sys_capability seed row', but the 6b-1 seeders are gone and no runtime reader consults a sys_capability row (record 6108465477). So the lint warning goes quiet for a capability the runtime never declares, and the requiredPermissions check fails closed at runtime: metadata the runtime silently drops. Owner if not filed: stage 8 (drop the seed branch and the hint text). Dedupe words: capability-reference-unknown, sys_capability seed, validate-capability-references, declared capability seed row",
        "carrier: stage 8 - noted, not filed: runtime refusal prose names the data route that stage 8 retires as the clone path ('POST /api/v1/data/sys_permission_set'): metadata-protocol/src/packaged-base-regime.ts:198 and plugin-security/src/packaged-permission-set-lock.ts:343.",
        "carrier: 8-post - noted, not filed: PLATFORM_CAPABILITY_NAMES import has moved to SP:124 (the 8-pre record says :127)."
      ],
      "deviations": [
        "status 'measured' is outside the os-dev template's enum (done | rework | blocked | needs_decision). The dispatch asks for it, and the template says it is a tool, so it is used as asked. The open questions are needs_decision in substance.",
        "No pnpm install in the worktree: nothing was built or run, and the measurement is git grep and file reads only. No PR, no label write, by the dispatch.",
        "Dispatch vs .claude/agents/os-dev.md: no conflict that changes an action. The file's PR deliverable does not apply to a measure-only claim, and the dispatch's 'zero MCP write tools' matches the file's write-tool rule. The claim amendment 6110606037 names this branch."
      ],
      "files_changed": [],
      "waiting_on": [
        "PM: answers to E1-E5 before 6b-2 (E2, E4) and stage 8 (E1, E3, E5) are dispatched"
      ]
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat answer: the stage-8 tier measurement (6110720794) is accepted · the landing order is corrected · E2 goes to a C9 part-3 measure · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T15:47Z

    Re-read on main 55382dc02a, and on the 6b-2-pre branch at 92bda9e9dd. E1, E3, E4 and E5 hold as reported:

    • E1: Setup's three type: 'object' nav entries are at security-plugin.ts:1541-1543. The permission-set write-through is registered at :4359-4376. The projector's evaluator echo is gated on the row write (permission-set-projection.ts:906-911).
    • E3: the branch keeps permission_set_id as an optional Field.lookup('sys_permission_set').
    • E4: both example binders read the catalog rows and write junction rows on kernel:bootstrapped.
    • E5: the inventory's authored categories say "converted by C3" (organization-ownership-inventory.ts:333-350), while core converts only bindings and active flags.

    This is the seat's second ordering error on this card. Stage 0 (6095755866) put 7a ahead of stage 8 for the same reason it had put 7b there, and 7b's stop (6106747036) already showed why that fails.

    Answers:

    • E1 → A. Stage 8 also unwires the projector, the permission-set write-through, the position write-through and the backfill registrations. 7a and 7b then follow as dead-code deletions and may be built in parallel.
      • Premise for the stage-8 dev to verify: a metadata-door permission save reaches the catalog without syncEvaluatorRegistry. If it does not, that is a measured stop with options, not a fix.
    • E3 → A. Stage 8 makes permission_set_id a Field.text. The column and its data stay for C7b.
    • E4 → A. 6b-2 deletes both bind-position-sets.ts files and their registrations. Its dev verifies that each example position's sets are declared on the position, so the dogfood suites keep their grants.
    • E5 → A. 7a and 7b each first move their row-to-definition mapping into core as a pure function beside convertPositionBindingRows, with pins, before deleting the boot module.

    E2: the owner exists. The path is not measured. objectui#7611 (C9) is in scope ("Pickers — assign a user to a position or a set … list the registry"); the measurement could not see objectui. objectui main has C9 part 1 (0df67f23): Setup's catalog pages, and their holder halves, read the registry. Not yet built:

    • The user page's two related-list Add pickers (sys-user.page.ts:206-211, :255-259) still list sys_position and sys_permission_set rows.
    • The related-list add.picker contract (RecordRelatedListProps.add, sdui.manifest.json:1549) names an object and nothing else.

    The position picker is already in this state on main. Since 6a, a fresh database lists no declared position in it. main is unreleased, and release-cut condition (c) of 6102862135 keeps this out of every cut. 6b-1c's capability page set the precedent (6104498446).

    Possible cycle, unmeasured:

    1. A registry source on the picker may need a spec change.
    2. objectui reads the spec only through its npm pin (^17), and spec 18 is not published (no next dist-tag; .changeset/pre.json is in pre mode).
    3. The spec-18 pin is itself inside condition (c).
    4. Stage 8 retires the objects these pickers name, so this lands before stage 8 at the latest.

    If every path needs a published spec 18, stage 8 → pickers → spec 18 → (c) → stage 7 → stage 8 is a cycle.

    So the seat dispatches C9 part 3, measure-only, now (claim amendment on objectui#7611). It measures:

    • the shortest path that puts both pickers on the registry;
    • whether that path needs a published spec;
    • how the repositories' release rules let objectui consume an unpublished spec change;
    • what each option leaves on main in the meantime.

    If the answer is a product or release choice, it goes to the maintainer as a decision card.

    6b-2 is not held by E2. It is built after 6b-2-pre lands, as planned. Its landing is gated on one of:

    • the E2 answer: pickers on the registry first;
    • or the maintainer ruling the grant picker's window the same way as the position picker's, kept out of every cut by (c).

    The landing order now:

    1. 6b-2-pre, in its gates.
      • Its user-page picker stays on rows, which still exist until 6b-2. The picker part of 6109520247 moves to E2.
    2. 6b-2, plus E4's binders; Tier H; its landing is gated on E2.
    3. 8, plus E1's unwiring and E3. About 2.95k lines, at the limit. Tests go in T-PRs ahead of it. It lands after E2's pickers.
    4. 7a ∥ 7b, as dead code, each with E5's re-home.
    5. 8-post.
    6. C7b.
    7. 6c, after 6b-2, 7a and stage 9, unchanged.

    Carried from the report:

    • validate-capability-references.ts:96-101 and its hint :106 → stage 8.
    • The refusal prose naming POST /api/v1/data/sys_permission_set (packaged-base-regime.ts:198, packaged-permission-set-lock.ts:343) → stage 8.
    • The PLATFORM_CAPABILITY_NAMES line drift (SP:124) → 8-post.
    • The unowned rows of the table (core's grants-cache watched set, plugin-sharing's bypassObjects, the object-permission entries, the settings and spec help text, core's isRowActive) → stage 8's dev places each one on its branch, measured. None is a new card.

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 15204,
      "status": "done",
      "branch": "claude/issue-15204-s6b2pre-grant-by-name",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22863",
      "session": "session_01MZuVPXoYeF1Pa3G4ERmTPz",
      "premise_still_valid": true,
      "summary": "Stage 6b-2-pre (grant by name) built and opened as draft PR #22863 (Part of #15204), head 92bda9e9d, merged with origin/main 55382dc02 after U2 (#22854) landed. sys_user_permission_set.permission_set is now the grant's required, writable reference (the set's machine name); permission_set_id is optional, and no writer fills it or derives anything from it. Every grant writer writes the name only and reads no sys_permission_set row to do it: the single-posture platform-admin promotion, the organization-admin reconcile (walled and single), plugin-auth self-registration, the verify RLS persona, and Setup's user-page picker (valueField name, linkField permission_set). grant-permission-set-name.ts is now the by-name refusal: a non-system write naming a set the security catalog does not hold answers 400 VALIDATION_FAILED, reference_not_found at permission_set (sibling of position-catalog-refusal); a missing name is the engine's 400 VALIDATION_FAILED / required. Readers moved to the name: delegated-admin-gate directGrantSet, last-admin-guard (permission_set_id left GRANT_STANDING_KEYS), uninstall cleanup (by name plus a legacy-id reach). Zero-row pins: org-admin reconcile walled and single, the data door, self-registration, verify persona; the promotion's grant is pinned name-only and its holder read with every row deleted. Kept on purpose, restrict-only, for grants written before the name column: the backfill, the D10 reference report, bootstrap leg C (now only when a legacy admin row exists), the org-admin revoke/superseded/duplicate legacy-id reach (duplicate check limited to rows the backfill would name), the uninstall legacy reach. The PR also edits skills/objectstack-data/rules/security.md, which prescribed writing the record id into permission_set_id: that makes it Tier H.",
      "tests": "Local, under scripts/pm/os-verify-lock.sh. After the origin/main merge: plugin-security full suite 196 files, 3973 passed / 45 skipped (VERDICT command-exit 0); dogfood suggested-binding-acceptance + zoo + window + me-apps + sharing-rule 5 files, 47 passed. Before the merge, on this branch: plugin-auth 136 files, 2774 passed; verify 30 of 31 files green in the full run, and rls-persona-grant.test.ts (rewritten) 3 passed on its own; platform-objects 69 files, 1083 passed; rest 277 files, 5454 passed; plugin-hono-server 28, plugin-sharing 41, organizations 12 files green; cli unit tier 281 files, 4185 passed; cli integration package-install-local-uninstall-cleanups 16 passed (it grants by name and the uninstall revokes it). Dogfood FULL (first run, 18m05s): 247 files: 235 passed, 1 skipped, 11 red, all id-only grant fixtures; fixed and re-run 11/11 green (4 + 7; the 7 first hit a transient spec dist rebuild by my own gate sweep, a run that measured nothing, then passed on re-run). Typecheck green: plugin-security, plugin-auth, verify, platform-objects, dogfood, rest, plugin-hono-server, plugin-sharing, organizations, cli. Generators: node scripts/check-i18n-bundles.mjs --write (en regenerated; zh-CN/ja-JP/es-ES values for the two columns hand-rewritten), pnpm gen:system-context-census. No ablation run: the refusal's red direction is pinned directly by its NEGATIVE cases (code VALIDATION_FAILED + status 400 + field code) on a real engine.",
      "mcp_calls": "4 — read-only: mcp__github__search_pull_requests x2 (duplicate check: 'permission_set_id', '15204'), mcp__github__pull_request_read get_files on #22854 (overlap check), and ToolSearch loading. Zero MCP write tools.",
      "api_writes": "3 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs (relay run 38152812443; POST /repos/objectstack-ai/objectstack/pulls draft + POST /repos/objectstack-ai/objectstack/issues/22863/assignees marchtian; body read back 10201 bytes identical); (2) scripts/pm/label-write.mjs --issue 22863 --assign marchtian (read-back matched; 0 write calls, idempotent no-op); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/15204/comments via scripts/pm/post-stamped.mjs. Plus git push (not REST).",
      "gates": {
        "derived": 106,
        "ran": 106,
        "reconcile": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt: 106 derived families accounted for, 106 run, 0 NOT-MEASURED",
        "red_then_fixed": [
          "node scripts/check-system-context-census.mjs: row 23c cited the retired #settle; rewritten, gen:system-context-census, green",
          "pnpm check:query-options-erasure: test surface 236 to 239 from three new as-any casts; casts removed, back at 236, green"
        ],
        "prerequisite_then_green": [
          "node scripts/check-plugin-teardown-shape.mjs --self-test (shallow clone; fetched 621a4876, green)",
          "pnpm check:dual-build-cjs-loads (unbuilt dists; built, green)",
          "pnpm --filter @objectstack/spec run check:skill-examples (client-react unbuilt; built, green)"
        ],
        "post_merge_rerun": [
          "check-system-context-census",
          "check-i18n-bundles",
          "check-adr-0087-registration --base origin/main",
          "check:nul-bytes"
        ]
      },
      "line_budget": {
        "changed_lines": 2693,
        "additions": 833,
        "deletions": 1860,
        "files": 91,
        "limit": 3000,
        "base": "merge-base 55382dc02"
      },
      "open_questions": [
        {
          "question": "Setup's Grant-permission-set picker writes the name now, but still LISTS sys_permission_set rows. objectui's record:related_list Add picker (pinned 0df67f237c5a, packages/plugin-detail/src/RelatedList.tsx) reads object records only (add.picker.object through the data source; C9's registry reads live in the metadata-admin pages, not in this picker). Once 6b-2 deletes the seeders, a fresh database's picker lists nothing. What does objectui need?",
          "options": [
            "A — A catalog source for the related-list Add picker: a spec key on RecordRelatedListProps.add.picker naming a metadata type (for example a 'permission' catalog source read from GET /api/v1/meta/permission, value = name), plus objectui support, then sys-user.page.ts points at it. Cost: one spec key (contract change, review), one objectui change, one objectstack page edit; small.",
            "B — Setup's grant UI moves to the C9 catalog pages (the permission-set editor's holders section already writes grants by name, per objectui#12089); the user page's grant affordance is dropped or links there. Cost: objectui only, plus removing the add block here; loses grant-from-the-user-page.",
            "C — Leave the row picker until 6b-2 and accept an empty picker on fresh databases. Rejected: it declares an affordance that does not work."
          ],
          "recommendation": "A, filed by the seat on objectui#7611 with its objectstack spec half. Real need: assigning a set from the user page is Setup's core flow and must survive 6b-2. Long-term: one picker source keyed to the catalog serves positions too (the position picker still lists sys_position rows, the same gap). AI-error: a declared metadata source enforced by the renderer beats a page pointing at a table that empties. Startup focus: no new gate; a single key with an existing consumer (this page) and B as the fallback if the key is refused."
        },
        {
          "question": "The dispatch says nothing derives anything from permission_set_id. Kept, restrict-only, for grants written before the name column: the one-time backfill (D10's own rewrite), the D10 catalog reference report, the bootstrap's leg C (withholds promotion while a legacy admin grant is unnamed), the org-admin revoke / superseded-variant / duplicate reach, and the uninstall reach. Retire them now or with the column?",
          "options": [
            "A — Keep them; they retire with the column in C7b (the grantSetNameOf docblock already records that plan). No grant is conferred through an id.",
            "B — Delete them now. Cost: on an upgraded deployment's first boot (the backfill runs at kernel:bootstrapped, after kernel:ready's bootstrap and reconcile) a second platform admin can be promoted, demotions and posture flips can leave a legacy organization_admin grant standing, and legacy rows the backfill names stop being deduplicated."
          ],
          "recommendation": "A. Real need: upgraded databases hold exactly these rows (measured in the backfill's own tests). Long-term: ADR-0131 D10 sanctions deleting mirror data only after the rewrite is verified; the column drop is that point. AI-error: each reach is labelled restrict-only and fails closed. Startup focus: nothing new is built; this is the existing transition the column drop ends, not a new transition piece (#22601 B forbids dual-write and fallback reads that CONFER; none remain)."
        },
        {
          "question": "Should a write that carries permission_set_id be refused now, or left inert until the column goes?",
          "options": [
            "A — Leave it inert: stored, read by nothing that grants; the column drop (C7b) retires it.",
            "B — Refuse a non-null permission_set_id on insert and on a changing update (400 VALIDATION_FAILED at that field). Cost: a refusal path and its pins for a column already scheduled to go, plus every fixture that still writes it."
          ],
          "recommendation": "A. The name is required, so an id-only write is already refused loudly; an id written beside a name confers nothing. A refusal for a field about to be dropped is a gate with a lifetime of one stage."
        },
        {
          "question": "The unique index still names (user_id, permission_set_id, organization_id); by-name grants store NULL there, so the database no longer refuses a duplicate by-name grant (platform writers check by name first; the data door does not). Where does the by-name unique key land?",
          "options": [
            "A — With the column drop (C7b): replace the index with (user_id, permission_set, organization_id) after deduplicating existing rows in the operator-run ceremony.",
            "B — Now: add the by-name unique index in this stage. Cost: index creation fails on any database holding two grants of one name for one user and organization (per-organization copies make that reachable), i.e. a data migration this stage is barred from."
          ],
          "recommendation": "A. The dispatch bars data migration here, and only the C7b ceremony can dedupe before constraining."
        }
      ],
      "out_of_scope_findings": [
        "carrier: stage 8 (junction retirement) · noted, not filed — sys_position_permission_set.permission_set_id stays a required lookup; measured writers on this tree: platform-objects pages/sys-position.page.ts 'Bind permission set', examples app-crm and app-showcase src/security/bind-position-sets.ts, U2's suggestion accept in suggested-audience-bindings.ts, and the anchor-binding gate in security-plugin.ts (~7078). Since stage 1 the junction grants nothing.",
        "carrier: stage 8 / whoever next touches packages/spec/src/ui/component.zod.ts · noted, not filed — the TSDoc on RecordRelatedListProps.add (line ~1593) names an 'Assigned Users' list keyed by relationshipField permission_set_id; no page declares it, prose only.",
        "carrier: the objectui#7611 seat item (open question 1) · the user page's POSITION picker (sys-user.page.ts, object sys_position, valueField name) has the same row-listing gap the grant picker has."
      ],
      "deviations": [
        "Edited skills/objectstack-data/rules/security.md (outside the expected landing zone): this change makes its prescription (write the record id into permission_set_id) refused, and os-dev requires fixing a shipped text the round falsifies. That makes PR #22863 Tier H (maintainer approval), and the body carries the 维护者速读 section; file 216 to 213 lines, package 3721 to 3718.",
        "Also edited outside the listed zone, as producers' tests and docs this change falsified: content/docs/permissions/permission-sets.mdx and system-context.mdx (census row 23c), and test fixtures in dogfood, rest, cli, plugin-hono-server, plugin-sharing and organizations that wrote id-only grants.",
        "spec ui/component.zod.ts not edited (measured as TSDoc prose with no consumer) — no spec regeneration in this PR.",
        "The dispatch's write list names label-write with --assign marchtian; run as given, it was a no-op (pr_create had already set the assignee). No label was written; the dispatch named none.",
        "No conflict found between this dispatch and .claude/agents/os-dev.md; the harness attribution reminder's PR footer form yielded to os-dev's session-URL footer, as the file directs.",
        "PR #22863's body says the first full dogfood run had '236 files green'; the run's own line reads 235 passed, 1 skipped, 11 failed (247). os-dev forbids PATCHing the body; the seat may correct that one figure."
      ],
      "files_changed": 91,
      "waiting_on": [
        "PM: same-head contract-tier review (Clause-②: yes (narrowing))",
        "maintainer: Tier H approval (skills/** path)",
        "seat: file open question 1 on objectui#7611"
      ],
      "measured_on": "objectstack branch head 92bda9e9d (origin/main 55382dc02 merged)"
    }

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat review: PR #22863 (stage 6b-2-pre, grant by name) at 92bda9e9d: one patch round for two published texts; then the Tier H kit

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T16:05Z. Cloud dev session_01MZuVPXoYeF1Pa3G4ERmTPz. Report: 6110797712. Amendment and answer: 6109520247.

    Checklist. I read it on GitHub and on the tree, not from the report.

    • PR form:

      • base main, draft;
      • line 1 is Part of #15204, line 2 is Clause-②: yes (narrowing);
      • the whole body is scanned: no closing keyword next to any card number;
      • the assignee is marchtian.
    • Scope:

      • 91 files, +833 / −1,860 = 2,693 changed lines, under 3,000. No file is deleted.
      • No content/docs/releases/, no CHANGELOG.md, no docs/adr/**.
      • check-governed-merges --pr 22863: one governed path, skills/objectstack-data/rules/security.md, so this is Tier H: the maintainer merges, or an account in GOVERNED_APPROVERS approves and the seat lands it.
    • Sentences checked against the diff:

      • 15204-grant-by-name-security.md: plugin-security minor, BREAKING, ADR-0087 not-required. It covers the before and after, both refusals with their envelopes, the platform writers, the unchanged backfill, and the remedy.
      • 15204-grant-by-name-writers.md: plugin-auth, verify and platform-objects patch.
      • permission-sets.mdx: the grant example now writes the name.
      • system-context.mdx: row 23c is rewritten, and the census goes 122 → 123 sites and 104 → 105 symbols.
      • The skills hunk now prescribes the name, says not to write permission_set_id, and drops the id-lookup step.

      All agree with the diff.

    • Refusal pins assert code and status (grant-permission-set-name.test.ts:152, :192, :259).

    • Contract review: PASS, 6110902443, on 92bda9e9dd0ebc0b2f02962003ab99ed66c97c67. It found the first two sentences of 6109520247 met. The third, "the Setup grant picker lists the security catalog", is not on this head; 6110777865 moved it to E2 (objectui#7611 C9 part 3), as a gate on 6b-2's landing, not on this PR.

    • The PR body's dogfood count read 236 green on the first run. It was 235 passed, 1 skipped and 11 red; the 11 were fixed to green. The seat corrected the line in the body; the report flagged it.

    Patch round (sent to the dev). The review checklist fixes, in the round, a published text the round makes false. Two are:

    1. The spec TSDoc on RecordRelatedListProps.add (packages/spec/src/ui/component.zod.ts ~:1597-1600). Its "canonical use", "Assigned Users" on a permission set with relationshipField=permission_set_id, now writes a grant with no permission_set, which this head refuses 400 VALIDATION_FAILED (required). It is rewritten by name, with a patch changeset for @objectstack/spec.
    2. The dangling-reference example in content/docs/api/error-catalog.mdx (~:286-297). It shows permission_set_id labelled "Permission Set"; this head relabels that field "Permission Set (legacy id)" and tells authors not to write it. It moves to a lookup that outlives the cutover.

    The PR body itself called the TSDoc "prose only; left untouched". The seat reads the spec's shipped TSDoc as published (the review checklist's published-surface rule: the spec ships src/**/*.zod.ts, and a false TSDoc there is a defect), so it does not stay.

    The new head needs a same-form contract-review PASS (a delta review of the patch) and green CI. Then the terminal kit:

    • needs-user-decision on the PR;
    • the final maintainer brief as a PR comment;
    • review requests to os-zhuang and hotlong.

    The seat does not flip it ready or enqueue it. 6b-2's build round goes out when this lands.

    Carried, not this PR's:

    • The by-name unique key, after C7b's dedupe. Today the data door can store a duplicate by-name grant, which is additive and grants nothing more.
    • The catalog-unreadable path stores the write with a warning, as the engine's referential check fails open (error-catalog.mdx). The resolver fails closed.
    • The junction's permission_set_id writers → stage 8.

    Generated by Claude Code

  16. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat note · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T16:23Z. This follows E2 of 6110777865.

    C9 part 3's measurement (objectui#7611 6111055760) confirms E2. On a fresh main database:

    • the user page's position picker lists 0 of the 16 declared positions;
    • the grant picker still matches its registry, 17 of 17, until 6b-2.

    The path is a product choice, filed as #22865 (the seat recommends B2: a registry field widget, 2 PRs, no spec change and no publish).

    • 6b-2's landing and stage 8 wait on that ruling and the PRs that follow it.
    • B1 or B2 both land before 6b-2.
    • The cycle E2 named is real only for path A, under release-cut condition (c) as written.

    Generated by Claude Code

  17. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    Addendum: patch round for PR #22863 (stage 6b-2-pre). The PM asked for it after contract review 6110902443. Text only.

    • New head: d453ea50d7f894e4bf451b51778df0a952f8500c, on claude/issue-15204-s6b2pre-grant-by-name. Plain push: no rebase, no force.
    • packages/spec/src/ui/component.zod.ts: the TSDoc on RecordRelatedListProps.add now gives its canonical example by name: relationshipField=permission_set, relationshipValueField=name, picker.object=sys_user, linkField=user_id. It is still one sentence. Changeset .changeset/15204-grant-by-name-spec-tsdoc.md (@objectstack/spec patch, text only).
    • content/docs/api/error-catalog.mdx: the dangling-reference example now uses sys_user_permission_set.user_id → sys_user. Its message, label and constraint were measured from the engine's real refusal (assertReferencesResolve, a non-system insert on a real ObjectQL engine over SQLite):
      {"status":400,"code":"VALIDATION_FAILED","fields":[{"field":"user_id","code":"reference_not_found","message":"User: no sys_user record has id \"usr_missing\"","label":"User","constraint":{"target":"sys_user"},"value":"usr_missing"}]}
      The probe was a throwaway test file, deleted afterwards and never committed.
    • Commands, all exit 0:
      • pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated under the lock: "All 14 generated artifacts are up to date", nothing regenerated.
      • The 14 families the new paths derive: check-spec-docblock-symbol-anchors (and --self-test), and the spec checks check:api-surface, check:authorable-surface, check:browser-reachable-entries, check:dual-source-exports, check:entry-nameability, check:export-origins, check:exported-any, check:llms-txt, check:objectui-pin-citations and check:skill-refs. Plus check:pm-prior-rulings and check:spec-parsed-alias.
      • The docs and changeset checks:
        • check:doc-anchors
        • docs-audit/check-affected-docs
        • docs-audit/check-drift-comment
        • check:docs-spec-enumerations
        • spec check:docs
        • check:doc-authoring
        • check:changeset-gate-self-tests
        • check-empty-changeset
        • check-changeset-no-major
        • check-adr-0087-registration
        • check:nul-bytes
      • dispatch-gates --ran: 120 of 120 derived families accounted for, 0 NOT-MEASURED.
    • Size: 94 files, 850 additions / 1,867 deletions = 2,717 changed lines (limit 3,000).
    • Nothing else changed. api_writes for this round: 1 (this comment). The PR body is not edited.

    Generated by Claude Code

  18. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Claim amendment · epic lane epic:#15194 · session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T16:25Z. This dispatches a measure-only run on release-cut condition (c) of 6102862135, specifically its half "objectui's spec-18 pin". Nothing is built, and no PR is opened.

    • Why: objectui#7611's part-3 measurement (6111055760, its Q2) found that this half cannot clear as written: objectui can pin spec 18 only after a publish, and (c) bars that publish. The half exists for F1, the "does not match the spec" banner on a position that declares permissionSets (objectui#7611 6104671088). F1 was measured on objectui's own console against npm spec 17.7.0. An objectstack release builds its console through scripts/build-console.sh, with this repository's spec injected (OBJECTSTACK_SPEC_DIST).
    • Measure: build the console the way a release does, at .objectui-sha on main, and confirm the injection landed with its own assertion. Boot a showcase, open a position that declares permissionSets in Setup and in Studio's editor, and say whether the banner shows. Run a control against the non-injected console.
    • Outcome:
      • If the banner does not show, the seat proposes rewording that half of (c) to "the console an objectstack release ships shows no banner". The maintainer confirms.
      • If it shows, whether to cut a next prerelease early is the maintainer's release decision.
    • Form: no PR; one os-dev-report comment here.
    • Developer: a subagent of this seat, mode:subagent.

    Generated by Claude Code

  19. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Claim amendment · epic lane epic:#15194 · session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T16:48Z. This opens stage CD, the permission-set clone door.

    Why. On 2026-10-10 the seat answered objectui#7611 Q1 → A (objectui#7611 6097763840): clone-to-customize after stage 8 is "a server door. It copies the whole definition under a new name and drops the keys the server declares (adminScope, isDefault)", and it "lands as an objectstack stage before stage 8 (services and rest; Clause-②: yes, so it owes a contract review)". Governing text: ADR-0126 §7.1 (amendment ruling 2), "clone is a whole-definition sibling under a new name with no linkage to the base". The seat never staged it. objectui#7611's census (6111307114) found the gap and measured that Clone breaks at 6b-2 on a fresh database, because it reads a sys_permission_set row by name.

    The landing order now (amends 6110777865):

    1. 6b-2-pre (feat(plugin-security,plugin-auth,verify,platform-objects)!: a permission-set grant names its set by name — no writer fills permission_set_id (#15204 stage 6b-2-pre) #22863, Tier H, awaiting the maintainer).
    2. CD, built now in parallel, landing before 6b-2.
    3. 6b-2, whose landing waits for:
    4. Stage 8, then 7a ∥ 7b, then 8-post, then C7b.

    Stage CD:

    • Branch: claude/issue-15204-clone-door; one draft PR, Part of #15204.
    • Clause-②: expected yes (widening), because a new route accepts what nothing accepted before. A same-head contract review is owed.
    • Model: the flow precedent POST /automation/:name/clone (packaged-base-regime.ts:45).
    • Developer: cloud, container M.
    • Out of scope: retiring clone_permission_set and ActionParamSchema.carryOver, which stay for stage 8.

    Generated by Claude Code

  20. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    Posted 2026-10-11T16:52Z · release-cut condition (c), its "objectui's spec-18 pin" half (F1), measure-only · developer: a subagent of the epic PM session_01Rerax7QTjKMPCUZxQUtPFR, under claim amendment 6111133457. No branch, no PR.

    {
     "issue": 15204,
     "part": "measure-only, release-cut condition (c) of 6102862135, its half \"objectui's spec-18 pin\" (claim amendment 6111133457)",
     "status": "measured",
     "branch": null,
     "pr": null,
     "session": "session_01Rerax7QTjKMPCUZxQUtPFR (mode:subagent; the epic PM's session id)",
     "premise_still_valid": true,
     "verdict": "NO. The console an objectstack release builds does not show F1. Built the way both release lanes build it (scripts/build-console.sh, this tree's spec injected), at .objectui-sha 4997995022f8 on objectstack main 55382dc02a, it shows no 'does not match the spec' banner on any of 4 editor pages for positions that declare permissionSets: Setup's catalog editor and the generic editor, in package scope and unscoped. The control is objectui's own build of the same objectui tree with nothing injected, so it bundles npm spec 17.7.0. On the same pages it shows the banner 4 times out of 4, with F1's exact text. A position that declares no permissionSets (finance) shows no banner in either build. A positive control on the injected build makes the banner fire (2 errors) when an invalid name is typed into the new-position form. So the injected build's validator works, and its silence on auditor is a real reading.",
     "summary": "Measured on objectstack origin/main 55382dc02a, in a detached worktree. The objectui pin is 4997995022f8, built from my own shallow clone of objectui at that commit (OBJECTUI_ROOT), so the shared /home/user/objectui was not used. (1) The release build is `pnpm objectui:build`, cut-rc.yml's exact step; release.yml runs the same script. Its injection assertion passes: \"Console bundle carries THIS tree's @objectstack/spec, and only it\". release.yml's two follow-up checks also pass on that dist: check:console-sha and check:console-injection --require-stamp, both exit 0. A targeted byte probe agrees. spec 17.7.0 refuses the key through a tombstone sentence, \"`permissionSets` is not a Position field\". That sentence is in 0 of the injected bundle's 2,474 JS assets and in 2 of the control bundle's. (2) Booted a fresh showcase (`pnpm dev -- --fresh -p 41873`) serving each dist in turn and signed in as the seeded admin. Each measurement ran in Playwright: injected 0 of 4 banners, control 4 of 4. (3) The release lanes cannot ship the control bundle. assert-console-spec-injection.mjs exits 1 on it (the build path), and check:console-injection --require-stamp exits 1 on its dist (release.yml's cache-restore path). Proposed rewording for (c)'s half is in `proposed_wording`. Nothing was built for review, and there is no PR.",
     "proposed_wording": "Replace (c)'s half \"objectui's spec-18 pin\" with: \"the console an objectstack release ships shows no 'does not match the spec' banner on a position that declares `permissionSets` (F1)\". Then add a status line: \"Met. Measured on objectstack main 55382dc02a at objectui pin 4997995022f8 (os-dev-report on #15204). Both release lanes build the console through scripts/build-console.sh with this tree's spec injected. A bundle that still carries the published spec fails the build (assert-console-spec-injection.mjs) or the restored-dist check (check:console-injection --require-stamp). objectui's own npm spec pin is objectui's release obligation (its AGENTS.md section 9 major sync). It is not an objectstack cut condition.\" The rest of (c), C9 (objectui#7611) with stage 7, is unchanged.",
     "readings": {
      "trees": "objectstack origin/main 55382dc02a (worktree /home/user/objectstack-f1-measure, detached). .objectui-sha = 4997995022f8e65946f6362b7434685539cc72b7 (objectui commit 'feat(app-shell): a ref-multi:permission widget picks declared permission sets by name (objectui#12126) (#12136)'). objectui's lockfile resolves @objectstack/spec 17.7.0 (pnpm-lock.yaml at the pin). apps/console declares ^17.0.0 and app-shell declares ^17.6.0.",
      "release_build": "Command: OBJECTUI_ROOT=SCRATCH/objectui-src, then `bash scripts/pm/os-verify-lock.sh -c 'pnpm objectui:build'` (OS_VERIFY_LOCK_SLOT=f1-measure, NODE_OPTIONS=--max-old-space-size=4096). Lock VERDICT command-exit 0, held 366s. Output lines: \"Console will bundle @objectstack/client from /home/user/objectstack-f1-measure/packages/client\"; \"Console will bundle @objectstack/spec from /home/user/objectstack-f1-measure/packages/spec\"; \"Bundle canary 'import/jobs' present\"; \"Single-zod canary: exactly one zod version literal {major:4,minor:6,patch:5} in .../vendor-objectstack-DJOwzPV3.js (2474 assets/*.js files read)\"; \"Console bundle carries THIS tree's @objectstack/spec, and only it. present (injected only): 'ADR-0112 registered code of the optimistic-lock refusal.' -- 27 of 28 injected-only descriptions are in the bundle; absent (vendored only): 'Collect multiple values (id array)' -- and all 20 published-only descriptions are absent\"; \"@objectstack/console dist ready (64400 KB) from objectui@4997995022f8\". After it, release.yml's own follow-ups: `pnpm check:console-sha` exit 0 (\"Console dist matches the objectui pin (objectui@4997995022f8)\"), and `node scripts/check-console-injection.mjs --require-stamp` exit 0 (\"the dist carries this tree's copy, and not the published one\").",
      "assertion_location": "scripts/assert-console-spec-injection.mjs (probe derivation in scripts/console-spec-probes.mjs). build-console.sh runs it after the copy to packages/console/dist. release.yml re-asks on a cache hit through `pnpm check:console-injection --require-stamp` (scripts/check-console-injection.mjs).",
      "lanes": "cut-rc.yml step 'Build the vendored Console SPA at the committed pin' runs `pnpm objectui:build`, and its publish step runs `pnpm run release` (= build + build-console.sh + release-publish.sh). release.yml's publish job restores a dist cache keyed on .objectui-sha, build-console.sh and the zod range; on a miss it runs `bash scripts/build-console.sh`, and either way it runs `pnpm check:console-injection --require-stamp`. The runtime image (docker/Dockerfile) installs the published @objectstack/cli, which depends on @objectstack/console, so the image serves the same released dist.",
      "targeted_probe": "The 17.7.0 tombstone sentence \"`permissionSets` is not a Position field -- a position is only the named distribution point (ADR-0090 D3) ...\" is what F1's banner prints. Files carrying it: vendored spec dist/index.mjs 1; this tree's packages/spec/dist/index.mjs 0 (this tree's PositionSchema keeps guidance only for users, parent and permissions); injected bundle 0 of 2474 JS assets; control bundle 2 (vendor-objectstack-BAU-oTR3.js, identity-DARLL74T.js).",
      "control_build": "In the same objectui tree at the pin: `bash scripts/pm/os-verify-lock.sh -c 'env -u OBJECTSTACK_SPEC_DIST -u OBJECTSTACK_CLIENT_DIST pnpm --filter @object-ui/console run build'`. VERDICT command-exit 0, held 119s. No injection plugin activity appears in its log. The release lane's assertion, pointed at this bundle (--injected packages/spec, --vendored the objectui tree's node_modules spec, --objectui the tree, --assets the control assets), exits 1: \"Built console still carries the PUBLISHED @objectstack/spec ... (15 of 20 published-only descriptions), the first of them: 'Collect multiple values (id array)'\". check:console-injection --require-stamp on the control dist exits 1: \"carries no .objectstack-injection.json stamp\". Both release paths refuse it.",
      "boot": "The first `pnpm dev -- --fresh -p 41873` was refused by check-dev-prereqs (\"8 of 67 workspace packages declare an entry point under dist/ that is not on disk\"). It was cleared with `turbo run build --filter=!@objectstack/docs --concurrency=2` under the lock (73 of 73, 63 cached, VERDICT 0). Then 3 boots of the same command, each started under the lock and healthy (GET /api/v1/health 200) after about 14s. The boots served: injected, control, injected again. Each boot's check:console-sha step passed, and each printed 'Console: http://localhost:41873/_console/'. Admin sign-in (admin@objectos.ai) POST 200. GET /api/v1/meta/position/auditor answered 200 with permissionSets [showcase_auditor] and _diagnostics {valid: true}. GET /api/v1/meta/position lists 16 positions; 9 declare permissionSets (8 showcase positions plus everyone).",
      "browser_injected": "Playwright 1.63.0, chromium /opt/pw-browsers/chromium, 1440x1000, signed in through the console form. The banner is counted by its own data-testid, metadata-validation-banner, after network idle plus 3s, with the timezone first-run dialog dismissed. Each page is also checked for render: the position name and its showcase_ set value are visible. Results: Setup catalog editor /_console/apps/setup/metadata/position/auditor?scope=environment: 0 banners. Generic editor in package scope ...auditor?package=com.example.showcase: 0. Generic editor unscoped .../position/auditor: 0. Setup catalog contributor?scope=environment: 0. Setup catalog finance?scope=environment (no permissionSets): 0. The Permission Sets field renders 'Showcase Auditor showcase_auditor'.",
      "browser_positive_control": "Injected build, /_console/apps/setup/metadata/position/new?scope=environment: 0 banners before typing. After 'Bad Name!' is typed into Name: 1 banner, \"This metadata does not match the spec -- 2 validation error(s). Name: Identifier must be lowercase snake_case, starting with a letter, ... Label: Required text value\". The injected bundle's client validator and banner both work.",
      "browser_control": "Control build, same 5 pages: auditor in catalog scope, package scope and unscoped, plus contributor: 1 banner each, 4 of 4. The text on each: \"This metadata does not match the spec -- 1 validation error(s). (root): Unrecognized key(s) on this position: `permissionSets`. * `permissionSets` is not a Position field -- a position is only the named distribution point (ADR-0090 D3); capability arrives via runtime bindings (`sys_position_permission_set` rows, created in Setup or by an app's kernel:ready binder). Packages SUGGEST bindings via `isDefault` on a permission set ...\" This is F1's text. finance: 0 banners. The control is lit.",
      "studio_surface": "The pin has no other editor surface for positions. Studio's design surface has only the data, automations and interfaces pillars (StudioDesignSurface.tsx line 261, PILLARS). The zero-app 'Studio / metadata designer' route /_console/metadata/position/auditor redirected to /_console/home on this boot, which has apps installed (measured). The stock showcase serves the apps showcase_app, setup and account (GET /api/v1/meta/app). So 'Studio's generic editor' was measured as the metadata-admin generic editor in its two non-catalog scopes. The Studio list links into the package-scoped one, and F1's own second URL was the unscoped one.",
      "cache_lag": "release.yml's console cache key does not hash spec content. A restored dist can therefore carry a spec older than the cut; check-console-injection.mjs's header records that lag as accepted. It cannot bring F1 back under the current pin, or any later one. The pin 4997995022f8 first lands in ea9fd99dd3 (#22794), and `git merge-base --is-ancestor bfc15d275ba3 ea9fd99dd3` (stage 1, #22723) exits 0. That exit is self-proving on this shallow clone. So every main tree that carries this pin contains stage 1's PositionSchema."
     },
     "screenshots": "SCRATCH = /tmp/claude-0/-home-user/32814588-78fe-5719-9936-bdd41d0c754f/scratchpad/issue-15204. Under SCRATCH/shots: injected-setup-catalog-auditor.png, injected-generic-pkg-auditor.png, injected-generic-unscoped-auditor.png, injected-setup-catalog-contributor.png, injected-setup-catalog-finance.png, injected-positive-control-new-position-bad-name.png, control-setup-catalog-auditor.png, control-generic-pkg-auditor.png, control-generic-unscoped-auditor.png, control-setup-catalog-contributor.png, control-setup-catalog-finance.png, plus injected-results.json, control-results.json and injected-b-results.json. Build logs: SCRATCH/console-injected.log and console-control.log. They are local to this container.",
     "tests": "Measure-only. No source diff, so no gate families are owed. Commands: `pnpm install --frozen-lockfile` (VERDICT 0). `turbo run build --filter=@objectstack/client... --filter=@objectstack/spec... --filter=@objectstack/example-showcase^... --concurrency=2` (60 of 60 cached, VERDICT 0). `pnpm objectui:build` (VERDICT 0, assertion green). Control console build (VERDICT 0). `pnpm check:console-sha` (0). `check:console-injection --require-stamp`: injected 0, control 1. The assertion against the control bundle: 1. Full workspace build (73 of 73, VERDICT 0). Three boots under the lock (VERDICT 0 each). Browser runs: measure-f1.cjs on injected and on control (exit 0 each), measure-f1b.cjs and positive-control.cjs on injected (exit 0 each). Every heavy step ran through os-verify-lock with OS_VERIFY_LOCK_SLOT=f1-measure. NOT MEASURED: a console built by GitHub's runners (this is the same script, run locally); cloud's console (see Q2).",
     "cleanup": "Each of the 3 servers was stopped by its own recorded process group (19893, 20592, 21473). After each stop the group was empty, and a curl to port 41873 was refused (exit 7). Neither shared checkout was edited: /home/user/objectstack's git status is clean, and /home/user/objectui's worktree list shows only itself. Removed before posting: the scratch objectui clone and the two scratch dist copies. Removed right after posting, because post-stamped runs from the worktree: the worktree's .cache, packages/console/dist, node_modules, and the worktree itself. Screenshots and logs stay in SCRATCH.",
     "deviations": [
      "The dispatch says both \"never fetch into them\" and \"from /home/user/objectstack, git fetch origin main\". I followed the explicit worktree recipe. That fetch updated the shared refs/remotes/origin/main, which already read 55382dc02a, and nothing else.",
      "build-console.sh's mode 2 would have used ../objectui, the shared checkout: it fetches into it when the pin is missing, and it adds a worktree registration there. So the build ran in mode 1, with OBJECTUI_ROOT pointed at my own shallow clone at the pin. cut-rc.yml does the same with its own clone.",
      "`pnpm dev` needed a full workspace build first (check-dev-prereqs). It ran under the lock, mostly cached."
     ],
     "mcp_calls": "0. No MCP GitHub tool was called.",
     "api_writes": "1: this comment, POST /repos/objectstack-ai/objectstack/issues/15204/comments, through scripts/pm/post-stamped.mjs. Reads were REST GETs through gh api: comments 6111133457, 6102862135, objectui 6104671088, 6111055760 and 6104663917, and the objectui#12089 PR body. There was also one git fetch of origin main, and one shallow fetch of objectui at the pin into my own scratch clone.",
     "open_questions": [
      {
       "question": "Q1 (the seat's; the maintainer confirms): how should (c)'s half \"objectui's spec-18 pin\" read, now that a release-built console measures no F1 banner?",
       "options": [
        "A. Reword it to the shipped artifact: 'the console an objectstack release ships shows no does-not-match-the-spec banner on a position that declares permissionSets', with a status line saying it is met, anchored to this measurement. Real need: F1's harm is a released console calling a valid position invalid, and this measured condition names exactly that. It is met today: 0 of 4 pages, against 4 of 4 on the control. Long term: it names the artifact, not a mechanism, and rests on the injection objectstack already built for this exact skew. That injection fails closed on both release paths, measured. AI error: a checkable condition with an existing instrument. A seat re-runs this measurement instead of chasing a pin. No new gate. Startup: one line of text, no PR, no publish.",
        "B. Delete the half. Its intent is met by construction. Real need: the same as A. Long term: equally sound, but it drops the explicit record that F1 was weighed for releases, and the anchor to re-measure if the injection design ever changes. AI error: neutral. Startup: the leanest.",
        "C. Keep it as written. Real need: it gates on objectui's npm pin, which does not reach the console an objectstack release ships (measured). Long term: it is a self-loop. objectui pins spec 18 only after a publish, and (c) bars that publish (6111055760, cycle.second_loop). AI error: it invites seats to chase a pin that cannot move before a cut, or to press for an early publish. Startup: it stalls every cut that contains stage 1, for a harm the release does not have.",
        "D. Keep it, and cut a `next` prerelease before (c) clears so that objectui can pin 18. Real need: none, for F1, which the release console already avoids. Long term: it bends release sequencing for a reason that is not there. AI error: a release act no AI seat may perform (Prime Directive #15). Startup: an extra release act, plus an objectui pin PR."
       ],
       "recommendation": "A. Real need: it states the harm the maintainer cared about, and that harm is measured absent on the artifact users receive. Long term: it keeps contract-first focus on the shipped artifact, and the injection plus its two fail-closed guards already hold it, with no workaround and no new mechanism. AI error: a measurable condition, not a mechanism to chase; I recommend no new gate, since the existing assertion and require-stamp check already refuse a non-injected release bundle. Startup: zero build cost, and it removes the self-loop that otherwise blocks a cut for no user-visible reason. B is acceptable if the seat prefers fewer conditions; the difference is only the record. objectui's spec-18 pin then follows objectui's own major-sync rule at its next release, without being a cut condition."
      },
      {
       "question": "Q2 (the seat's): should (c) also cover consoles that an objectstack release does not build, namely objectui's own published @object-ui packages bundled by some other host, and cloud's console overlay (build-console.sh mentions 'a cloud/objectos Docker overlay that replaces dist/')?",
       "options": [
        "(i) No. Keep (c) on the objectstack release, as the seat answer 6104671088 framed it: 'no objectstack release that carries this console may ship while the banner calls a valid position invalid'. Real need: no consumer of those other routes was measured or named. Long term: each repo's release owns its own console. AI error: neutral. Startup: no scope growth.",
        "(ii) Yes. Extend (c) to cloud's console after measuring it in the cloud repository. Real need: unknown, NOT MEASURED, because this run has no cloud tree. Long term: one cross-repo condition. AI error: a condition nobody here can check. Startup: widens the release gate onto another repository."
       ],
       "recommendation": "(i). Every axis favours keeping the condition on the artifact this repository ships, and (ii) would gate on a surface no one has measured. If the seat wants cloud covered, the first step is a measurement in cloud, not a condition here."
      }
     ],
     "out_of_scope_findings": [
      "carrier: objectui's next release, which syncs its major to 18 under objectui AGENTS.md section 9 · noted, not filed. objectui's own non-injected console build still shows F1 against a stage-1 backend: measured here, the control. So does its Vite dev console on :5180, per 6104663917. Neither is an objectstack release surface. dedupe words: position permissionSets unrecognized key banner; objectui spec 17.7.0 pin",
      "carrier: the seat, Q2 · noted, not filed. Whether cloud's console overlay bundles a spec that accepts permissionSets: NOT MEASURED, with no reach. dedupe words: cloud console overlay spec injection",
      "noted, not filed: release.yml's console-dist cache key omits spec content. That lag is recorded as accepted in check-console-injection.mjs's header, and it cannot reintroduce F1 under the current pin (see readings.cache_lag). No filing class applies."
     ]
    }

    Generated by Claude Code

  21. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat ACCEPT: PR #22863 (stage 6b-2-pre, grant by name) at d453ea50d: Tier H, awaiting a human merge

    Epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian), seat epic:#15194, 2026-10-11T16:54Z. Cloud dev session_01MZuVPXoYeF1Pa3G4ERmTPz. Report 6110797712; patch-round addendum 6111119186, whose size line is corrected to 94 files, +845 / −1,867 = 2,712. Seat review: 6110944288.

    • Checklist: as in the seat review 6110944288, re-read on the new head.

    • The patch round's delta 92bda9e9dd..d453ea50d7 is one commit and three files, +12 / −7, text only:

      • the spec TSDoc's "Assigned Users" example is now by name, with a @objectstack/spec patch changeset;
      • error-catalog.mdx's dangling-reference example now uses sys_user_permission_set.user_id → sys_user, and its message, label and constraint match the engine's refusal.
      • The seat read the diff.
    • Contract review: PASS on this head, 6111245592, a delta round that adopts 6110902443's judgments. Both records are same-form.

    • Evidence on the merged head:

      • full dogfood, 249 files: 248 passed, 1 skipped; 2,017 tests: 2,008 passed, 9 skipped;
      • plugin-security, 196 files / 3,973 passed;
      • the patch round's spec and docs families all exit 0;
      • dispatch-gates --ran 120 / 120.
    • CI on d453ea50d: 38 success and 4 skipped by design (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke), 0 red; every run is pinned to d453ea50d7.

    • PR body: the seat corrected the dogfood count, the size, the changeset list and the stale TSDoc sentence.

    • Tier H: skills/objectstack-data/rules/security.md. The seat does not flip it ready or enqueue it. In this same stroke:

      • needs-user-decision goes on the PR;
      • the maintainer's final brief is posted as a PR comment;
      • reviews are requested from os-zhuang and hotlong.

      The terminal is the maintainer's merge, or an authorized approval after which the seat lands it.

    Then:


    Generated by Claude Code

  22. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    Contributor

    Seat note: release-cut condition (c)'s half "objectui's spec-18 pin" is measured met in substance. The seat proposes a rewording; the maintainer confirms · epic PM session_01Rerax7QTjKMPCUZxQUtPFR (marchtian) · 2026-10-11T16:57Z

    The measurement is the os-dev-report above, under amendment 6111133457. It was taken on main 55382dc02a at the objectui pin 4997995022f8.

    • Injected build: the console built the way both release lanes build it, through scripts/build-console.sh with this tree's spec injected.
      • It shows 0 of 4 F1 banners on positions that declare permissionSets, across Setup's catalog editor and the generic editor, in package scope and unscoped.
      • The injection assertion passes, and so do release.yml's check:console-sha and check:console-injection --require-stamp.
    • Control: objectui's own build of the same tree, with nothing injected, so npm spec 17.7.0.
      • It shows the banner 4 of 4, with F1's exact text. The control is lit.
      • Both release paths refuse that control bundle: the assertion exits 1, and --require-stamp exits 1.
    • Positive control: an invalid name typed into the new-position form makes the banner fire on the injected build, so its silence on auditor is a real reading.

    Why the half cannot stand as written:

    • objectui can pin spec 18 only after a publish, and (c) bars that publish. The loop is the one in objectui#7611 6111055760, cycle.second_loop.
    • The harm it guarded against, a released console calling a valid position invalid (6104671088), is measured absent from the console an objectstack release ships.

    Proposal (the measurement's option A). Replace "objectui's spec-18 pin" in (c) with:

    the console an objectstack release ships shows no "does not match the spec" banner on a position that declares permissionSets (F1)

    Status: met.

    • Measured on main 55382dc02a at pin 4997995022f8.
    • Both release lanes build the console with this tree's spec injected, and a bundle that still carries the published spec fails the build assertion or the restored-dist check.
    • objectui's own npm spec pin is objectui's release obligation, under its AGENTS.md §9 major sync. It is not an objectstack cut condition.

    Unchanged:

    • The rest of (c): C9 (objectui#7611) with stage 7.
    • (b), C7b.
    • (e), the two changeset corrections.

    Scope. (c) stays on the artifact this repository ships (the measurement's Q2 → (i)). Cloud's console overlay is not measured here. If cloud should be covered, the first step is a measurement in cloud, which is with the maintainer, alongside the cloud .objectui-sha hold already reported.

    The seat applies this rewording when the maintainer confirms. Until then, (c) stands as written.


    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