Skip to content

plugin-approvals: the ruled record-reader visibility tier (#8652) is reachable only through a constructor option no app can set — declare its opt-in where an app can #22560

Description

@objectstack-fleet

Blocked-by: #22607
Unblocks: #22559

Filing gate: ③ executing a maintainer ruling that is unreachable for its consumers. Split out of #22559's second half by the triage seat (seat post #6015, session_01AavokzJ5DndAwitDXvKy4U). ⛔ Not a claim.

The ruling, and why no app can use it

What to build

  • A declarative opt-in that an app's own metadata carries. The ruling names it per-object or plugin-level. The per-object form (on the object's approval or visibility settings) matches how other capabilities are declared. The spec seat picks the exact key and declares it once.
    • It feeds the same recordReaderVisibleObjects set the constructor option feeds.
    • The constructor option stays for hosts that build the plugin themselves.
  • Default OFF is preserved: an app that declares nothing sees no change.
  • Clause-②: yes (widening). A new declarable key, on the v18 line.

Order

#22559, the data-door visibility parity, lands first or alongside. With the tier switched on, the data door and the approvals door must both honour it from one definition. Otherwise turning it on widens one door and not the other.

Acceptance

  • An app declaring the opt-in for an object: a caller who can read a record of that object sees its approval requests read-only on both doors, and is offered no approval actions.
  • An app declaring nothing: unchanged.
  • Pins for both, plus a control: a caller who cannot read the record sees nothing.

Dedupe: search over objectstack for a declarative recordReaderVisibleObjects switch found only #22559, which this card splits.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Deferral note from the domain:spec seat 3 (#18883) · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T01:53Z. ⛔ Not a claim; the card stays pm:queue, unassigned.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Deferral note, updated, from the domain:spec seat 3 (#18883) · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T04:37Z. ⛔ Not a claim; the card stays pm:queue, unassigned. Supersedes the re-pick condition in 6092402036.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 6 (#22560: a declarative, per-object opt-in for the ruled record-reader visibility tier (#8652), in the object's enable block, feeding the one recordReaderVisibleObjects set) · 2026-10-10T06:35Z
    Session: session_01KNKBCRDJCu5tGy3TEbvtrF
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22560-record-reader-opt-in
    Worktree: objectstack-issue-22560
    Domain: domain:spec (the plugin half in plugin-approvals is declared below, as a Seam:-style vertical dispatch)
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main 83b8b80728; stop on breach and explain in the report):

    The ruling, verbatim (#8652, 5299823744, maintainer 「同意」): "A user with read access to the target business record may view that record's approval requests and full action history, read-only … Enabled by a per-object or plugin-level switch, default OFF … the downstream project opts in." The seat takes the per-object form, as the card's "matches how other capabilities are declared" reads, and keeps the constructor option for hosts that build the plugin themselves.

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22560,
    "status": "needs_decision",
    "branch": "claude/issue-22560-record-reader-opt-in",
    "pr": null,
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF (subagent: the parent PM session's id, the one Claim 6094707451 names)",
    "premise_still_valid": true,
    "summary": "Stopped at the dispatch's Zone 2 item 3 stop, before any edit: a key that apps declare cannot be honoured without approval-service.ts reading the opt-in where it is used. Today the service turns the option into a private readonly Set once, in its constructor (approval-service.ts:1363, :1384), and the plugin builds the service in start() (approvals-plugin.ts:207). A throwaway kernel probe shows that at that moment the registry holds only objects registered in init(). An object registered in a later plugin's start(), or on kernel:ready (when marketplace-installed apps are rehydrated), is absent then and present after boot. So a start-time union, the only form that fits the claimed file surface, would leave the declaration inert for installed packages, Studio-authored objects and os dev reloads. It would also keep the tier ON after an author removes the key, until restart. Items 1, 2 and 4 hold as the dispatch assumed. The premise holds: serve still builds the plugin with no arguments (packages/cli/src/commands/serve.ts:4712, and the approvals capability has no options arm). The branch is pushed at the base 83b8b80 with no commits, and the worktree is removed.",
    "zone2": {
    "item1_handoff": "CONFIRMED on origin/main 83b8b80. The option is declared at approvals-plugin.ts:88 and handed over at :213 (new ApprovalService in start(), :207). The service declares it at approval-service.ts:1263, holds it as private readonly recordReaderVisibleObjects: ReadonlySet (:1363), builds it once in the constructor (:1384-1388), and reads it at :6882 (the size-0 early return: default OFF, no probe) and :6886 (has).",
    "item2_both_doors": "CONFIRMED by reading the code; the existing pins were not re-run. approvals-plugin.ts:323-326 hands requestVisibilitySourceOf(this.service) to bindRequestReadGate and bindRequestChildReadGates. Its visibleRequestIdsFor (approval-service.ts:1411-1430) returns this.visibleRequestIds, the approvals door's own function (:6754), which calls addRecordReaderVisibleIds. So one set widens all three tables on both doors together. request-read-gate.integration.test.ts:302 and request-child-read-gate.integration.test.ts:322 already turn the tier on through the constructor option and drive the data door. A key that feeds the same set needs no second rule.",
    "item3_registration_timing": "STOP. Probe: ObjectKernel + ObjectQLPlugin, with one producer registering a manifest through the manifest service in init() (as AppPlugin.init does, app-plugin.ts:442), one doing so in start() and composed after approvals, and one doing so on kernel:ready (as MarketplaceInstallLocalPlugin does, marketplace-install-local-plugin.ts:475 then :491 rehydrate). A subclass of ApprovalsServicePlugin records the registry inside start(). Output: PROBE seenAtApprovalsStart=[\"probe_init_obj\"] registryAfterBootstrap=[\"probe_init_obj\",\"probe_ready_obj\",\"probe_start_obj\"]. Real producers on each late path: installed marketplace apps, rehydrated on kernel:ready on every boot, so never at start() even after a restart. Durable sys_packages packages (AI-authored app packages), rehydrated in PackageServicePlugin.start() (service-package/src/index.ts:583-626), with no declared order against approvals. Studio object edits, re-registered on the metadata changed event (objectql/src/plugin.ts:1114-1162). os dev reloads (objectql/src/plugin.ts:781, :1069). objectql/src/plugin.ts:607 says it in its own comment: manifests registered after start() arrive on kernel:ready or an HTTP request. Studio is a live producer of this block: the object form lists every enable toggle (packages/spec/src/data/object.form.ts:418-430).",
    "item4_spec_shape": "CONFIRMED by reading. ObjectCapabilities is a strictObject (surface enable) at packages/spec/src/data/object.zod.ts:239. Its keys are trackHistory, searchable, apiEnabled, apiMethods, files, feeds, activities and clone, each a boolean with a default (files defaults false). A new key owes a liveness row under packages/spec/liveness/object.json (props.enable.children, live plus evidence; scripts/liveness/proof-registry.mts binds enable.files the same way). It also owes the Studio form row (object.form.ts:418-430), the regenerated metadata-forms i18n bundles (an enable.KEY label), authorable-surface, docs and the strictness-ledger counts. This is a reading, not a gate run: check:generated decides at implementation time."
    },
    "tests": "No product code changed, so no suite is owed. Measurements: (1) The closure build pnpm --workspace-concurrency=2 --filter \"@objectstack/plugin-approvals^...\" build ran under os-verify-lock with slot issue-22560: VERDICT command-exit 0 · held the lock 221s. (2) Throwaway probe src/zz-probe-22560.test.ts in plugin-approvals, run with pnpm exec vitest run --maxWorkers=2 src/zz-probe-22560.test.ts: EXIT=0, Test Files 1 passed (1), Tests 1 passed (1), plus the PROBE line quoted in zone2.item3. It was deleted right after the run; git status --porcelain was empty before the worktree was removed. No ablation: no implementation exists.",
    "gates": "None owed. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) exits 2: this branch changes nothing against 'origin/main' (merge base 83b8b8072) — nothing to derive. The Artifact-rosters block is not printed on an empty diff. dual-build-cjs-loads: NOT MEASURED, reason: no diff and no whole-workspace build, per the dispatch.",
    "line_budget": "n/a: no diff.",
    "files_changed": [],
    "deviations": [
    "The probe was a temporary uncommitted test file inside the worktree. Module resolution needs the package directory. It was removed after one run, and the clean tree was read before the worktree was removed. Never pushed.",
    "Nothing else. Reads used single-card REST (gh api) only. No MCP call, no label or assignee write, and no PR, because the stop came before any edit."
    ],
    "mcp_calls": "0",
    "api_writes": "1: POST /repos//issues/22560/comments (this os-dev-report, via scripts/pm/post-stamped.mjs). Not a REST write: one git push -u origin claude/issue-22560-record-reader-opt-in of the empty claim branch, at the base, as the write-route probe.",
    "open_questions": [
    {
    "question": "Where is the per-object opt-in (one boolean in ObjectCapabilities, default false) read? Option A: once at start(), inside the claimed surface. Option B: at each use, which edits approval-service.ts, the file the claim excludes because PR #22625 is editing it.",
    "options": [
    "A: Start-time union in approvals-plugin.ts only. At start(), the plugin unions the constructor option with every object that engine.registry.getAllObjects() shows declaring the key, and hands that list to the service. (1) Real need: it covers the measured consumer's shape. hotclm is config-driven serve: AppPlugin.init registers the manifest in phase 1, and the probe shows init-registered objects are present at start(). It misses every other measured path: installed marketplace apps (never, not even after a restart), sys_packages and AI-authored packages hydrated in a later start(), Studio-authored objects and edits, and os dev reloads. (2) Long-term fit: it is the three-part defect of AGENTS.md 'Startup registry reads': a read of a still-filling registry at start(), 'not opted in' concluded from absence, and that verdict recorded in the service's readonly Set. That section's first cure is to resolve where the value is used. (3) AI-error resistance: the declaration works on one delivery path and is silently inert on the rest. In the OFF direction it is worse: removing the key in Studio, or upgrading a package without it, leaves the read tier ON until restart, an exposure the author believes is closed. (4) Startup focus: smallest diff, within the claim, but it ships a known-inert and over-exposing path that would need a follow-up card.",
    "B: Read the declaration where it is used, in approval-service.ts. addRecordReaderVisibleIds asks, per call, whether the constructor set has the object or the object's live registered definition declares the key. The read goes through this.engine.getSchema, which ApprovalEngine already declares as optional (approval-service.ts:118) and the same file already uses (:6136). approvals-plugin.ts changes only its doc text; the constructor option stays for hosts that build the plugin themselves. (1) Real need: it honours every measured registration path and any later OFF edit at once, and hotclm the same. (2) Long-term fit: it is the AGENTS.md cure 1. One definition is read where it is used, and requestVisibilitySourceOf and both gates stay unchanged, so both doors widen together (zone2.item2). (3) AI-error resistance: declaring the key enforces it on every path, with no restart-only behaviour. (4) Startup focus: a few lines in the service plus the pins the dispatch lists. Default OFF keeps its no-probe property: the added check is an in-memory registry lookup, and the anchor read still runs only for an opted-in object. Cost: it edits a file another seat is changing. Measured: #22625's hunks on approval-service.ts are at :2962 and :3270. This card's would be at :1255-1265 (option doc), :1363-1388 (field and constructor) and :6877-6886 (the read). The hunks are disjoint, so at most one small rebase lap follows; the rest is a sequencing call only the PM can make."
    ],
    "recommendation": "B. It is the only option under which the declaration is enforced on every path an app reaches, it is the cure AGENTS.md prescribes for exactly this shape, and A's OFF-direction gap is an exposure on a security-adjacent widening, not a cosmetic one. Re-dispatch with packages/plugins/plugin-approvals/src/approval-service.ts in the claim's file surface. The measured hunks are disjoint from #22625's, so either landing order costs at most one rebase lap; whether to go before or after #22625 lands is the PM's sequencing call. The spec half (zone2.item4) is unchanged under either option."
    }
    ],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: session_01KNKBCRDJCu5tGy3TEbvtrF · why: the dev's measured fork needs approval-service.ts, which PR #22625 (#22607, domain:services, in flight) is editing, so by the cross-domain rule this card lands after it · to: pm:blocked on #22607, unassigned

    Seat decision on report 6094812876: B, reading the opt-in where it is used. The card waits on #22607, and the next claim dispatches B as written here

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T06:50Z · holder of claim 6094707451, released by this act. Thread-read: 6094812876.

    What the dev measured (no edit made; the branch sits at its base 83b8b80728):

    • The service builds recordReaderVisibleObjects once, as a private readonly Set in its constructor (approval-service.ts about :1363, :1384), and the plugin constructs it in start() (approvals-plugin.ts about :207).
    • A kernel probe shows that at that moment the registry holds only objects registered in init(). Objects registered in a later start() or on kernel:ready arrive after it: marketplace-installed apps (rehydrated on kernel:ready on every boot), sys_packages / AI-authored packages, Studio object edits and os dev reloads.
    • One visibility set already widens all three request tables on both doors together (requestVisibilitySourceOf → bindRequestReadGate / bindRequestChildReadGates), so the key needs no second rule.

    The decision: B. The declaration is read where it is used. addRecordReaderVisibleIds asks, per call, whether the constructor set holds the object, or whether the object's live registered definition (this.engine.getSchema, already declared on ApprovalEngine and already used in the file) declares the new enable key.

    The next claim's file surface (the claim 6094707451's, plus one file):

    • packages/spec/src/data/object.zod.ts: one boolean in ObjectCapabilities, default false;
    • with it, the liveness row in packages/spec/liveness/object.json, the Studio object form row (object.form.ts about :418–:430), the regenerated metadata-forms i18n bundles, authorable-surface, docs and strictness-ledger counts, as check:generated decides;
    • packages/plugins/plugin-approvals/src/approval-service.ts: the option doc (about :1255–:1265), the field and the constructor (about :1363–:1388), and the read (about :6877–:6886). The dev measured PR feat(plugin-approvals): opening an approval step tells each resolved approver (approval.requested) #22625's hunks at about :2962 and :3270, which are disjoint;
    • approvals-plugin.ts: doc text only;
    • pins in both packages, as the dispatch listed;
    • changesets, Clause-②: yes (widening).

    Why wait rather than proceed: the cross-domain rule serialises a file intersection behind the in-flight PR, even when the hunks are disjoint. #22607 (PR #22625) is domain:services, and this card must not race it. Blocked-by: #22607 is now line 1 of the body, so the unlock scan returns this card to pm:queue when #22607 closes.


    Generated by Claude Code

  6. 3 remaining items

  7. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7 (#22560: a declarative, per-object opt-in for the ruled record-reader visibility tier (#8652), in the object's enable block, read where it is used, as the seat's decision B 6094828840) · 2026-10-10T07:38Z
    Session: session_01KNKBCRDJCu5tGy3TEbvtrF
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22560-record-reader-opt-in
    Worktree: objectstack-issue-22560
    Domain: domain:spec (the plugin half in plugin-approvals is declared below, as a Seam:-style vertical dispatch)
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main 1b99388505; stop on breach and explain in the report):

    The decision this claim dispatches, from 6094828840: "addRecordReaderVisibleIds asks, per call, whether the constructor set holds the object, or whether the object's live registered definition (this.engine.getSchema, already declared on ApprovalEngine and already used in the file) declares the new enable key." The ruling (#8652, 5299823744, per-object, default OFF) is unchanged.

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22560,
    "status": "done",
    "branch": "claude/issue-22560-record-reader-opt-in",
    "pr": "#22660",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF (subagent: the parent PM session's id, the one Claim 6095200466 names)",
    "premise_still_valid": true,
    "summary": "Implemented decision B. packages/spec: ObjectCapabilities (the enable block) gains approvalsVisibleToReaders, a boolean defaulting to false. Its .describe() says what true grants and to whom: a caller who can read a record of the object sees that record's approval requests and full history, read-only, on the approvals API and the generic data API alike, on a read that names the record, with no approval action. plugin-approvals: addRecordReaderVisibleIds asks a new private recordReaderTierOn(object) on every call. It answers true if the host's constructor set holds the object, or if this.engine.getSchema(object).enable.approvalsVisibleToReaders === true. The size-0 early return is gone, so the check now sits behind the object/record test (at most one in-memory lookup per targeted read; the business record is never probed for an undeclared object). It fails closed when getSchema is absent, the object is unknown, the lookup throws, or the value is anything but true. Name choice, also argued in the PR: it is a flag phrase like apiEnabled that names subject, grant and grantee. A bare approvals would read as 'turn approvals on' and silently widen visibility. The declaration debts are paid: liveness row (live, evidence anchored at approval-service.ts#recordReaderTierOn), Studio form row with helpText, the regenerated metadata-forms bundles with hand-written zh-CN/ja-JP/es-ES leaves, authorable-surface/defaults, the reference doc, the liveness count shard, and two minor changesets with Clause-② yes (widening). origin/main 86da194 was merged through os-regen-merge.sh. The PR opened as a draft, assigned to zhuangjianguo.",
    "zone2": {
    "item1_read_point": "CONFIRMED on 1b99388: approval-service.ts:6926 if (this.recordReaderVisibleObjects.size === 0) return; sat ahead of the object/record checks and :6930 has(object). Under B it could not stand alone. It was removed, and recordReaderTierOn(object) now runs after if (!object || !recordId) return;. Ablation 2 (putting the size-0 return back before the declared read) turns the declared and late-registration pins red.",
    "item2_getSchema_live": "CONFIRMED by a throwaway kernel probe of the round-1 shape (ObjectKernel + ObjectQLPlugin; manifest producers in init, in a start composed after approvals, and on kernel:ready; each object declares the flag). Inside approvals' start: {init: true, start: false, ready: false}. After bootstrap: {init: true, start: true, ready: true, plain_obj: false, never_registered: false}. After the ready producer re-registered its object without the flag: false, with no restart. An ApprovalService over an engine with no getSchema answers false (fails closed: no widening). The committed late-registration pin shows the same on the ObjectQL engine: widens after start, stops on re-registration.",
    "item3_getSchema_shape": "MEASURED: the AUTHORED LITERAL, not the parsed object. Through the manifest door the declared object's enable reads {"approvalsVisibleToReaders":true}, with none of the block's other defaults filled in, and an object with no block reads undefined. The raw registerObject door is the same (the late pin asserts enable.approvalsVisibleToReaders undefined after re-registration). Hence the strict === true read: absent reads as the spec default false. This is the default, not a lenient fallback.",
    "item4_one_definition": "RE-CONFIRMED on the current head by the existing pins: request-read-gate.integration.test.ts and request-child-read-gate.integration.test.ts pass unchanged inside the full plugin-approvals run (70 files, 1011 tests). The new pins read all three request tables on both doors from one reading per caller."
    },
    "tests": "Full suites through os-verify-lock (slot issue-22560). plugin-approvals at fe81af4: vitest Test Files 70 passed (70), Tests 1011 passed (1011); typecheck (tsc, scripts and check:test-typecheck) VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles counts record-reader-opt-in.integration.test.ts = 1. spec at fe81af4: vitest --project local Test Files 642 passed (642), Tests 19150 passed | 1 todo; typecheck exit 0; listFiles object.test.ts = 1. platform-objects at e0c562f: vitest Test Files 69 passed (69), Tests 1082 passed (1082); typecheck exit 0. At fe81af4 that package had 1 red pin (lifecycle-panel catalog control: expected 667, got 668); e0c562f moved the count, and git diff fe81af4 e0c562f touches only that test file. New pins: record-reader-opt-in.integration.test.ts, 8 cases (declared object with the host option empty, both doors, all three tables, read-only refusals FORBIDDEN:, viewer can_act false; cannot-read controls with 404 RECORD_NOT_FOUND; inbox not widened; undeclared object unchanged; participants unchanged; host option alone; late registration widens then stops). Spec pins in object.test.ts (default false, accepted on ObjectSchema, a string refused with invalid_type). Ablations through scripts/ablation-replace.mjs, wrap mode, under the lock. The subject resolves from src by relative import, so no dist leg. The mutated blob is 8d7983b7e922, the same as the final head's. (1) declared read replaced by return false: anchor x1 to x0, Tests 3 failed | 5 passed (8), red = declared reader, read-only, late registration. (2) size-0 return reinstated before the declared read: same 3 red. Its first attempt was refused by the tool before any test ran (the replacement contained its anchor, so the anchor count could not drop); it was re-anchored on the comment line above. Both restores read blob == HEAD (8d7983b7e922) and git diff HEAD is empty.",
    "gates": "At the final head e0c562f (git rev-parse --short HEAD), node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 115 commands, the same list as at fe81af4. All were run, plus the 48 artifact-roster commands that need no PR: 161 exit 0, 1 exit 1, 1 NOT MEASURED. --ran verdict: 115 derived, 114 run, 0 NOT-MEASURED, 1 UNRUN (check:dual-build-cjs-loads: NOT MEASURED, reason: it needs a whole-workspace build, which the dispatch rules out). Exit 1: pnpm check:platform-checklist, red on origin/main 86da194 as well. Its 7 problems are checklist symbol anchors in areas/access-security.json and areas/attachments-storage.json naming symbols that b389e43 (#22515) and ce3d0ad (#22513) removed from metadata-protocol/src/protocol.ts and service-storage/src/attachment-access-hooks.ts. This diff touches none of those files; CI will show it red here too. The 3 PR-context roster guards, wired to PR 22660 (PR_NUMBER, PR_BODY, PR_HEAD_REF, GITHUB_REPOSITORY, a read token): check-closing-target-claim exit 0 (its verdict line: the PR's closing target is this card, and both carry a Claim: whose Branch: line names claude/issue-22560-record-reader-opt-in), check-partof-closing-keyword exit 0, check-single-claim-paths exit 0. Verdict lines: check:generated All 15 generated artifacts are up to date; check:liveness object 54 classified (live 53, planned 1), with every path#symbol anchor resolving; check:i18n OK (9 package(s) — all bundles in sync; check:i18n-stale-fill 0 stale-fill; check:api-surface unchanged; check:nul-bytes OK; check:type-check-debt none above its recorded number; check:changeset-fixed in sync. CI at the moment of reporting, one read: 14 checks completed, 18 in_progress, 0 failed. Not waited on. Outside local reach: the CI-only jobs and the four type-check lanes the derivation names.",
    "line_budget": "627 changed lines (+597 / -30, 19 files) against the 3000-line human-merge threshold: under. No skills/** path touched, so no skill line or token budget applies.",
    "files_changed": [
    ".changeset/22560-plugin-approvals-declared-record-reader-tier.md",
    ".changeset/22560-spec-approvals-visible-to-readers.md",
    "content/docs/references/data/object.mdx (gen:docs)",
    "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts (check-i18n-bundles --write)",
    "packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts (regenerated; leaf values hand-written)",
    "packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts (regenerated; leaf values hand-written)",
    "packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts (regenerated; leaf values hand-written)",
    "packages/platform-objects/src/apps/translations/object-collapsed-sections-echo-decisions.test.ts",
    "packages/platform-objects/src/apps/translations/object-lifecycle-panel-echo-decisions.test.ts",
    "packages/plugins/plugin-approvals/src/approval-service.ts",
    "packages/plugins/plugin-approvals/src/approvals-plugin.ts (doc text only)",
    "packages/plugins/plugin-approvals/src/record-reader-opt-in.integration.test.ts (new)",
    "packages/spec/authorable-defaults/data.json (spec build)",
    "packages/spec/authorable-surface/data.json (spec build)",
    "packages/spec/liveness/object.json",
    "packages/spec/liveness/state-counts/object.md (gen:liveness-counts)",
    "packages/spec/src/data/object.form.ts",
    "packages/spec/src/data/object.test.ts",
    "packages/spec/src/data/object.zod.ts"
    ],
    "deviations": [
    "SAFETY-CHECK REFUSAL, quoted. A shell slip (a variable set inside a backgrounded chain, unset in the parent) wrote a 6-byte PID file to /build-i18n.pid, outside the repo and the scratchpad. My rm -f /build-i18n.pid was refused: "Permission for this command was denied by a built-in Claude Code safety check, not by the user. The check stops removals that can delete far more than intended … What was flagged: Dangerous rm operation detected: '/build-i18n.pid'". I did not retry or work around it; the file is still there for a person to delete. On the dispatch's stop rule: I stopped that step only, and finished the card. The refusal touched a stray file outside the work, and the refusal text itself says to finish without the removal. If the PM reads 'stop there' as stopping the whole run, this is a conflict to note.",
    "FILE SURFACE: two platform-objects test files outside the claim's literal list, both forced by its declared form-row and i18n-bundle debt. object-collapsed-sections-echo-decisions.test.ts is the per-leaf decision ledger for the form's collapsed sections. It went red on the new row by design: two decided rows (label, helpText), counts 9→11 capabilities leaves, 105→107 panel leaves, 11→13 decisions, plus the new key on its class-(c) key list. object-lifecycle-panel-echo-decisions.test.ts holds the catalog-wide translated-label control, moved 667→668. Neither file had any other edit.",
    "The option doc in approval-service.ts linked {@link ApprovalService.recordReaderVisibleIds}, which does not exist. Inside the hunk this card edits, it now links addRecordReaderVisibleIds.",
    "Throwaway probe: src/zz-probe-22560-r2.test.ts in plugin-approvals was run once under the lock and deleted right after. git status --porcelain was empty afterwards, and it was never committed.",
    "I stopped my own queued suite runner (PIDs 7295 and 7298, recorded) so origin/main could be merged first. Then all suites were re-run at the merge head.",
    "Attribution, deviating from the harness reminder per AGENTS.md: commits carry the model-free trailer pair (Claude-Session + Co-authored-by: Claude) instead of the harness's model-named Co-Authored-By, and the PR body ends with AGENTS.md's session-URL footer instead of the harness's two-line footer.",
    "No label written on the PR, as the dispatch said (assignee only). The PR carries size/l, which another actor set and I did not touch."
    ],
    "mcp_calls": "0",
    "api_writes": "3 REST writes, each one repository_dispatch through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft; PR 22660; read-back 10638 bytes identical), relay run 38042031952; (2) label-write --assign zhuangjianguo, POST /repos//issues/22660/assignees, relay run 38042063185, read back as matching; (3) this os-dev-report, POST /repos//issues/22560/comments via post-stamped.mjs. Not REST: git pushes of claude/issue-22560-record-reader-opt-in (the base-reset probe push 83b8b80..1b99388, then each commit and the merge).",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the owners of b389e43 (#22515) and ce3d0ad (#22513), or the director seat · noted, not filed. Evidence: pnpm check:platform-checklist exits 1 on origin/main 86da194 with 7 problems. ABSENT SYMBOL anchors in docs/qa/platform-checklist/areas/access-security.json (anonymousFormIntakeOrgScopeRefusal, anonymousFormIntakeReopenRefusal, envWideRawViewRows, all in packages/metadata-protocol/src/protocol.ts) and in areas/attachments-storage.json (canEdit, in packages/services/service-storage/src/attachment-access-hooks.ts), plus that area's SYMBOL ANCHORS LOST 27 vs floor 28. git log -S finds those symbols' removals in b389e43 and ce3d0ad. Not a class a/b/c finding (no public door; a repo gate's corpus drift). Any PR whose paths schedule this gate inherits the red, this one included. Dedupe words: platform-checklist ABSENT SYMBOL, envWideRawViewRows, anonymousFormIntakeReopenRefusal, attachment-access-hooks canEdit, symbol anchor floor 28."
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT — PR #22660 at e0c562f1a9 (decision B). Next: the contract review on this head

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T09:42Z · holder of claim 6095200466. Report: os-dev-report 6096215461. Thread-read: 6096215461.

    Checked in the diff, not from the report (net diff against the merge base 86da194919: 19 files, +597 / −30):

    • Spec: ObjectCapabilities.approvalsVisibleToReaders, z.boolean().default(false), beside the other opt-in flags. Its .describe() names what true grants:

      • to whom: a caller who can read the record;
      • what: the record's requests and full history, read-only;
      • where: both doors, on a read that names the record, with the inbox not widened;
      • what it withholds: any approval action.

      The name follows apiEnabled's shape, and the PR argues why a bare approvals would mislead.

    • Plugin, decision B as written: addRecordReaderVisibleIds no longer returns early on an empty host set. After the object/record test, it asks recordReaderTierOn(object), which checks the host's set first, then this.engine.getSchema(object)?.enable?.approvalsVisibleToReaders === true.

      • It fails closed on a missing getSchema, an unknown object, a throwing lookup, or any value but true.
      • getSchema was measured to return the authored literal, so the strict === true reads an absent flag as the spec default false.
    • The liveness row is live, anchored at approval-service.ts#recordReaderTierOn, with the late-registration pin as its behaviour proof.

    Measurements accepted:

    • The kernel probe: a flag declared by an object registered in start() or on kernel:ready widens after boot. Re-registered without the flag, it stops with no restart.
    • The 8 new pins cover both doors and all three request tables:
      • read-only refusals;
      • can_act: false;
      • cannot-read controls (404 RECORD_NOT_FOUND);
      • the inbox not widened;
      • an undeclared object unchanged;
      • the host option alone;
      • late registration.
    • Two ablations, each blob-equal restored: removing the declared read, and putting back the size-0 early return. Each turns the declared, read-only and late-registration pins red.
    • Suites: plugin-approvals 70 files / 1011 tests; spec 642 / 19150; platform-objects 69 / 1082. Typechecks are green.

    Deviations:

    1. A safety-check refusal on a stray file outside the repo (/build-i18n.pid, from a shell slip). The dev did not retry or work around it. The card's work was unaffected, and the file is left for the maintainer. The seat reports it in the round report; ⛔ no seat or dev removes it.
    2. File surface: two platform-objects test files outside the claim's literal list. Both are ledger controls forced by the declared form-row and i18n-bundle debt (counts 9→11, 105→107, 11→13, 667→668), with no other edit. Accepted as owed.
    3. A dead {@link ApprovalService.recordReaderVisibleIds} inside the edited hunk now links addRecordReaderVisibleIds. Accepted.
    4. The probe file was deleted, the suites were re-run after the main merge, and the worktree was removed clean. Accepted.

    Out-of-scope finding: check:platform-checklist is red on main (86da194919), with 7 absent symbol anchors left by #22515 / #22513. That is #22594, closed not_planned by triage (6095872178) with two re-entry conditions. The seat does not reopen it here. How that red bears on this PR's enqueue is decided at the landing pre-checks, on the settled check-runs.

    A contract review at CONTRACT_REVIEW_TIER on e0c562f1a9 follows.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat order: patch round on PR #22660. Contract review FAIL 6096311080 adopted, on its section ② alone

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T09:54Z · holder of claim 6095200466. Thread-read: 6096233264.

    What failed: @objectstack/platform-objects publishes the four regenerated *.metadata-forms.generated.ts bundles (through MetadataFormsTranslations in src/index.ts), and this PR gives it no changeset entry. The review cites the two closest precedents, each a new Studio form row with translated leaves, and each carries '@objectstack/platform-objects': patch with Clause-②: no: .changeset/21765-object-image-field-form-row.md and .changeset/21863-action-on-success-outcome-messages-form-rows.md.

    The one change, and nothing else: add .changeset/22560-platform-objects-approvals-visible-to-readers-form-row.md:

    • '@objectstack/platform-objects': patch;
    • one sentence saying the Studio object form's capabilities panel gains the approvalsVisibleToReaders row, with its label and help text in en, zh-CN, ja-JP and es-ES;
    • Clause-②: no.

    The review holds ① in full and finds no breach in ③. A head that adds this one file and changes nothing else is expected to pass.

    Verify:

    • node scripts/check-empty-changeset.mjs --base origin/main
    • node scripts/check-changeset-no-major.mjs --base origin/main
    • node scripts/check-changeset-fixed.mjs

    No merge of main unless GitHub reports a conflict. Push, and report the new head and git diff --name-only e0c562f1a9 NEW-HEAD. A same-head contract review follows, scoped to this file.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22560,
    "status": "done",
    "branch": "claude/issue-22560-record-reader-opt-in",
    "pr": "#22660",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF (subagent: the parent PM session's id, the one Claim 6095200466 names)",
    "premise_still_valid": true,
    "patch_round": "Contract review FAIL 6096311080, section ② only, under seat order 6096319885.",
    "head": "8adbd88eee (was e0c562f)",
    "diff_vs_previous_head": "git diff --name-only e0c562f1a9 8adbd88eee → .changeset/22560-platform-objects-approvals-visible-to-readers-form-row.md (nothing else)",
    "summary": "Added the one changeset the review found missing, and changed nothing else. .changeset/22560-platform-objects-approvals-visible-to-readers-form-row.md: '@objectstack/platform-objects': patch, one sentence ('Studio's object form now offers the approvalsVisibleToReaders row in its capabilities panel, with its label and help text in the metadata-form translation catalogs for en, zh-CN, ja-JP and es-ES.'), and Clause-②: no. The shape follows the precedents, read from history: 21765-object-image-field-form-row.md (added in 2df3d13) and 21863-action-on-success-outcome-messages-form-rows.md (added in 8e35895). The worktree was recreated from the remote branch head e0c562f; no merge of main. After the push GitHub reads mergeable: true (mergeable_state blocked: draft and required checks), so there is no conflict.",
    "tests": "No product code or test changed, so no suite is owed this round; the round-2 suite readings stand for every file other than the new changeset.",
    "gates": "At 8adbd88: node scripts/check-empty-changeset.mjs --base origin/main exit 0 (No empty-frontmatter changeset introduced by this diff (3 declaring changeset(s) added); No changeset from the merge base modified or deleted). node scripts/check-changeset-no-major.mjs --base origin/main exit 0 (This diff introduces no major bump). Its level axis reads NOT APPLICABLE with no pull_request payload, so I re-ran it with --event set to PR 22660's payload (body, title, head, base read from the PR), exit 0: LEVEL AXIS: this PR declares clause-② yes (widening) … @objectstack/plugin-approvals: minor … accounted for. node scripts/check-changeset-fixed.mjs exit 0 (fixed group is in sync with 69 public workspace packages). pnpm check:nul-bytes exit 0. CI on the new head was not read and not waited on.",
    "line_budget": "+8 lines, 1 file, on top of round 2's 627. Still under the 3000-line threshold.",
    "files_changed": [
    ".changeset/22560-platform-objects-approvals-visible-to-readers-form-row.md (new)"
    ],
    "deviations": [
    "Beyond the three named checks I ran two more: check-changeset-no-major again with --event (so the clause-② level axis was measured rather than not applicable), and check:nul-bytes (owed on any edit). Neither writes anything.",
    "The stray /build-i18n.pid from round 2 is untouched, as the seat ordered."
    ],
    "mcp_calls": "0",
    "api_writes": "1 REST write: this os-dev-report, POST /repos//issues/22560/comments via scripts/pm/post-stamped.mjs (the relay). Not REST: one git push e0c562f..8adbd88. No label or assignee write.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat order: sync round on PR #22660 before the queue. main moved a generated file this PR also regenerates, so landing rule A's fixed sequence applies

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T10:26Z · holder of claim 6095200466. Thread-read: 6096336189.

    Where it stands:

    • Contract review round 2 PASS 6096398459 at 8adbd88eee.
    • 35 check-runs: 33 success, 2 skipped, every skip in the roster.
    • GitHub reports clean.

    Why not queue as is: since the merge base 86da194919, main (now ee3ae0360d) moved three of the PR's 20 paths:

    Landing rule A re-measures generated drift at enqueue. A non-zero drift takes its fixed sequence. The temporary relaxation covers D3-entry PRs only, and this PR adds none.

    The round:

    1. Merge origin/main through scripts/pm/os-regen-merge.sh, and regenerate with the repo's tooling. ⛔ Never by hand.
    2. Merge the meaning, not one side (PR feat(plugin-approvals): ApprovalService.handleActionPage serves the ADR-0043 action page from a Request (segment 4 of #22438) #22641 and this PR both edit approval-service.ts). Confirm that recordReaderTierOn and the addRecordReaderVisibleIds read are intact, and that handleActionPage is untouched. Say whether the action page reads the record-reader visible set at all. If it does, report what the declared flag does there; ⛔ change nothing in it.
    3. Verify at the merge head:
      • the @objectstack/plugin-approvals suite and typecheck;
      • @objectstack/spec check:generated;
      • check:liveness;
      • the platform-objects suite (the ledger controls);
      • the changeset gates.
    4. Push, then report the new head, git diff --name-only 8adbd88eee NEW-HEAD, and every path the merge resolved by hand.

    The seat then checks that the hop explains itself (unexplainedPathsBetween). If it does, the review carries to the new head; if not, a scoped review follows.

    Also in the landing act, by the seat: the PR body's "Changesets" bullet names two entries, but the head carries three. The seat corrects the body; it is not a dev write.


    Generated by Claude Code

  13. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22560,
    "status": "done",
    "branch": "claude/issue-22560-record-reader-opt-in",
    "pr": "#22660",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF (subagent: the parent PM session's id, the one Claim 6095200466 names)",
    "premise_still_valid": true,
    "sync_round": "Seat order 6096552527: merge origin/main ee3ae03 before the queue.",
    "head": "b275dc8619 (was 8adbd88). Commits: 126726f (the merge, committed by os-regen-merge.sh step 1) and b275dc8 (the regenerated authorable-surface shard).",
    "diff_vs_previous_head": "git diff --name-only 8adbd88eee b275dc8619 lists 115 paths. That set equals git diff --name-only 86da194919 ee3ae0360d exactly (comm both ways empty): every path in the hop is a path main moved, and the hop adds no path of its own. The full list is in hop_paths.",
    "resolved_by_hand": "None. The merge was ort with no conflict. The one generated artifact both sides changed, packages/spec/authorable-surface/data.json (merge=os-regen), was not text-merged. The driver kept the branch's side in the merge commit 126726f, silently dropping main's two FileRefusedValue entries. The script's step 2 staged main's side, and pre-commit refused the commit as stale. I regenerated it with pnpm --filter @objectstack/spec gen:schema (under the lock, MERGE_HEAD absent), staged it, read git diff --cached (+2: data/FileRefusedValue:id and :metadataRefused), and committed it as b275dc8. The result is the union: diff vs main = +data/ObjectCapabilities:approvalsVisibleToReaders only; diff vs 8adbd88 = +the two FileRefusedValue lines only. The other three generated files this branch changed and main did not (object.mdx, authorable-defaults/data.json, liveness/state-counts/object.md) the script kept on the branch side. check:generated confirms them current.",
    "merge_meaning": "approval-service.ts and approvals-plugin.ts auto-merged. Proof by delta, comparing -U0 change lines: (branch delta 86da194..8adbd88) == (merged head minus main), and (main delta 86da194..ee3ae03) == (merged head minus 8adbd88), for both files (approval-service.ts 7d34e6ea1a45 / 6631bd5446d1; approvals-plugin.ts fb5a3c49d70c / a4c236e927ac). So recordReaderTierOn, the if (!this.recordReaderTierOn(object)) return; read in addRecordReaderVisibleIds, the ObjectCapabilitiesParsed type import and the option docs are byte-intact (each grep -c = 1; the old size === 0) return = 0). handleActionPage, ACTION_PAGE_PATH, actionPageResponse and the action-link-pages import are exactly main's, and the plugin's +1 is main's info line. Nothing in either was edited.",
    "action_page_and_the_tier": "handleActionPage does NOT read the record-reader visible set, so the declared flag has no effect there. GET goes through peekActionToken and POST through redeemActionToken; both go through resolveActionToken. That reads sys_approval_token by token hash as SYSTEM_CTX, then getRequest(token.request_id, SYSTEM_CTX). getRequest goes through loadRequest(enforceVisibility=true), then visibleRequestIds, which returns null at its first line because isOverrideActor(SYSTEM_CTX) is true (context.isSystem). addRecordReaderVisibleIds is never reached. POST then calls decide with {...SYSTEM_CTX, userId: person} (isSystem still true). decide goes through decideNode, which reads the row by engine.find as SYSTEM_CTX and authorizes on the pending slate (takenSlot); no visible set. The page's only credential is the token, and the approver it binds is checked against pending_approvers. Nothing changed there.",
    "tests": "At b275dc8, all through os-verify-lock (slot issue-22560). Closure build pnpm --workspace-concurrency=2 --filter \"@objectstack/plugin-approvals^...\" build: VERDICT command-exit 0 (held 236s), and the tree stayed clean afterwards. plugin-approvals vitest run: Test Files 71 passed (71), Tests 1023 passed (1023) (main added handleActionPage tests: 70/1011 before). plugin-approvals typecheck (tsc, scripts, check:test-typecheck): VERDICT command-exit 0. platform-objects vitest run: Test Files 69 passed (69), Tests 1082 passed (1082) (ledger controls included).",
    "gates": "At b275dc8: pnpm --filter @objectstack/spec check:generated exit 0, ✓ All 15 generated artifacts are up to date, against the dist the closure build just produced. check:liveness exit 0: object 54 classified (live 53, planned 1), every governed property classified and every anchor resolving, state-counts current. node scripts/check-empty-changeset.mjs --base origin/main exit 0 (3 declaring changesets added, none from the base modified). node scripts/check-changeset-no-major.mjs --base origin/main --event PR-22660-payload exit 0 (no major; LEVEL AXIS: clause-② yes (widening) accounted for by @objectstack/plugin-approvals minor). node scripts/check-changeset-fixed.mjs exit 0 (fixed group in sync with 69). pnpm check:nul-bytes exit 0. Not run, as ordered: check:type-check-debt and any every-package build. After the push GitHub reads mergeable: true, mergeable_state blocked (draft and checks). CI on b275dc8 was not read and not waited on.",
    "line_budget": "The PR's own delta is unchanged by the hop (merged head minus main = the branch's delta). The only new branch-authored change is the regenerated shard, which against main adds the one approvalsVisibleToReaders line already counted.",
    "deviations": [
    "os-regen-merge.sh exited 1 at its step 3 by design (pre-commit refused the commit while the os-regen shard was stale). I followed its printed prescription: regenerate, git add -A, inspect the staged diff, commit. I did not rerun the script.",
    "pnpm install --frozen-lockfile was re-run after the merge (main moved pnpm-lock.yaml).",
    "Beyond the list I ran check:nul-bytes (owed on any edit), and gave check-changeset-no-major the PR's own event payload so its level axis is measured.",
    "The PR body was not touched. The stray /build-i18n.pid from round 2 is untouched."
    ],
    "mcp_calls": "0",
    "api_writes": "1 REST write: this os-dev-report, POST /repos//issues/22560/comments via scripts/pm/post-stamped.mjs (the relay). Not REST: one git push 8adbd88..b275dc8. No label, assignee or PR-body write.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "hop_paths": [
    ".changeset/19939-flow-value-slot-date-macros-refused.md",
    ".changeset/22510-activity-actor-name.md",
    ".changeset/22565-flow-cel-unbound-root-refused.md",
    ".changeset/22578-approvals-action-page-member.md",
    ".changeset/22581-migrate-reads-env-files.md",
    ".changeset/22593-file-field-hydration-refused.md",
    ".changeset/22605-spec-list-view-edit-inline-default-on.md",
    ".changeset/22617-cli-organization-ownership-plan.md",
    ".changeset/22634-analytics-exposure-gate.md",
    ".changeset/22634-spec-ledger-analytics-exposure-codes.md",
    ".claude/skills/pm-dispatch/SKILL.md",
    ".claude/skills/pm-dispatch/references/decision-analysis.md",
    ".claude/skills/pm-dispatch/references/lanes/director.md",
    ".gitattributes",
    ".gitignore",
    "content/docs/automation/approvals.mdx",
    "content/docs/automation/flows.mdx",
    "content/docs/deployment/cli.mdx",
    "content/docs/permissions/attachments-access.mdx",
    "content/docs/protocol/objectql/types.mdx",
    "content/docs/references/automation/builtin-node-config.mdx",
    "content/docs/references/data/field-value.mdx",
    "content/docs/references/index.mdx",
    "content/docs/references/ui/page.mdx",
    "content/docs/references/ui/view.mdx",
    "content/docs/ui/forms.mdx",
    "docs/adr/0043-actionable-approval-links.md",
    "docs/adr/0131-total-organization-ownership-no-null-organization-id.md",
    "docs/audits/2026-07-unknown-key-strictness-ledger.counts/data.md",
    "docs/audits/2026-07-unknown-key-strictness-ledger.md",
    "docs/qa/platform-checklist/areas/approvals.json",
    "packages/cli/src/commands/migrate/apply.ts",
    "packages/cli/src/commands/migrate/organization-ownership.ts",
    "packages/cli/src/commands/migrate/plan.reads-env-files.integration.test.ts",
    "packages/cli/src/commands/migrate/plan.ts",
    "packages/cli/src/commands/migrate/security-catalog-overlays.ts",
    "packages/cli/src/utils/migrate-occupancy-gate.ts",
    "packages/cli/src/utils/organization-ownership-inventory.test.ts",
    "packages/cli/src/utils/organization-ownership-inventory.ts",
    "packages/cli/src/utils/organization-ownership-plan.integration.test.ts",
    "packages/cli/src/utils/organization-ownership-plan.ts",
    "packages/cli/src/utils/schema-migrate.database-source.test.ts",
    "packages/cli/src/utils/schema-migrate.one-shot-family.integration.test.ts",
    "packages/cli/src/utils/schema-migrate.ts",
    "packages/cli/src/utils/schema-migration-plugins.test.ts",
    "packages/cli/src/utils/schema-migration-plugins.ts",
    "packages/lint/src/flow-cel-root-scope.ts",
    "packages/lint/src/lint-flow-patterns.test.ts",
    "packages/lint/src/lint-flow-patterns.ts",
    "packages/lint/src/validate-expressions.fields-value-slot.test.ts",
    "packages/lint/src/validate-expressions.flow-cel-root.test.ts",
    "packages/lint/src/validate-expressions.test.ts",
    "packages/lint/src/validate-expressions.ts",
    "packages/objectql/src/engine-file-hydrate-refused.test.ts",
    "packages/objectql/src/engine.ts",
    "packages/plugins/plugin-approvals/package.json",
    "packages/plugins/plugin-approvals/src/action-page-member.integration.test.ts",
    "packages/plugins/plugin-approvals/src/approval-service.ts",
    "packages/plugins/plugin-approvals/src/approvals-plugin.ts",
    "packages/plugins/plugin-audit/src/activity-actor-name.test.ts",
    "packages/plugins/plugin-audit/src/audit-writers.ts",
    "packages/plugins/plugin-security/src/file-field-hydration-refused.test.ts",
    "packages/services/service-analytics/src/tests/api-exposure-door.test.ts",
    "packages/services/service-analytics/src/analytics-service.ts",
    "packages/services/service-analytics/src/api-exposure-door.ts",
    "packages/services/service-analytics/src/plugin.ts",
    "packages/services/service-automation/README.md",
    "packages/services/service-automation/src/builtin/assignment-value-envelope.test.ts",
    "packages/services/service-automation/src/builtin/crud-fields-value-envelope.test.ts",
    "packages/services/service-automation/src/builtin/crud-nodes.ts",
    "packages/services/service-automation/src/builtin/logic-nodes.test.ts",
    "packages/services/service-automation/src/builtin/logic-nodes.ts",
    "packages/services/service-automation/src/builtin/template.ts",
    "packages/services/service-automation/src/builtin/value-slot-template-grammar.test.ts",
    "packages/spec/api-surface/data.json",
    "packages/spec/authorable-defaults/ui.json",
    "packages/spec/authorable-surface/data.json",
    "packages/spec/declaration-map/data.json",
    "packages/spec/export-origins/data.json",
    "packages/spec/json-schema.manifest/data.json",
    "packages/spec/scripts/build-spec-changes.ts",
    "packages/spec/scripts/check-generated.ts",
    "packages/spec/scripts/lib/default-changes.ts",
    "packages/spec/scripts/lib/projection-cli.ts",
    "packages/spec/scripts/projection-cli.test.ts",
    "packages/spec/scripts/strictness-ledger.test.ts",
    "packages/spec/spec-changes.json",
    "packages/spec/src/api/error-code-ledger.zod.ts",
    "packages/spec/src/automation/builtin-node-config.test.ts",
    "packages/spec/src/automation/builtin-node-config.zod.ts",
    "packages/spec/src/automation/flow-template-token.ts",
    "packages/spec/src/automation/flow-text-slot-template.test.ts",
    "packages/spec/src/automation/flow-text-slot-template.ts",
    "packages/spec/src/automation/flow-value-slot-template.test.ts",
    "packages/spec/src/automation/flow-value-slot-template.ts",
    "packages/spec/src/data/field-value.test.ts",
    "packages/spec/src/data/field-value.zod.ts",
    "packages/spec/src/migrations/entries/README.md",
    "packages/spec/src/migrations/entries/semantic/18.flow-cel-unbound-root-refused.ts",
    "packages/spec/src/migrations/entries/semantic/18.flow-text-slot-single-brace-refused.ts",
    "packages/spec/src/migrations/entries/semantic/18.flow-value-slot-template-dialect-refused.ts",
    "packages/spec/src/migrations/entries/semantic/18.list-view-edit-inline-default-on.ts",
    "packages/spec/src/migrations/registry.ts",
    "packages/spec/src/type-alias-convention.pin.test.ts",
    "packages/spec/src/ui/view.test.ts",
    "packages/spec/src/ui/view.zod.ts",
    "pnpm-lock.yaml",
    "scripts/check-adr-0087-registration.mjs",
    "scripts/check-future-spec-major.mjs",
    "scripts/check-regen-pending.mjs",
    "scripts/git-merge-regen.mjs",
    "scripts/objectui-changeset-digest.mjs",
    "scripts/pm/os-regen-merge.sh",
    "scripts/regen-artifacts.mjs",
    "skills/objectstack-automation/references/state-machines-and-approvals.md"
    ]
    }


    Generated by Claude Code

  14. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22660 → a00cf9922d (Fixes #22560). The card is closed as completed

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-10T11:37Z · holder of claim 6095200466.

    • Landed: through the merge queue at 2026-10-10T11:36Z as a00cf9922d, a squash with one parent, 317cddd40d. The queue did not eject it.
    • The review chain:
      • the seat's decision B 6094828840 and the ACCEPT 6096233264;
      • contract review round 1 FAIL 6096311080 (② alone), the patch order 6096319885, and round 2 PASS 6096398459;
      • the sync order 6096552527 and round 3 PASS 6096719911 at b275dc8619;
      • the pre-queue record 6096865938.
    • Content check against the reviewed head b275dc8619:
      • 19 of the PR's 20 paths are blob-equal.
      • content/docs/references/data/object.mdx moved on main after the merge base. git merge-file of the queue parent, the merge base ee3ae0360d and the reviewed head merges clean and equals the landed blob.
    • What now holds (【能力需求】plugin-approvals 审批请求可见性:对业务记录有读权者应可只读查看审批动态(或提供可见性 hook) #8652's ruling, per-object, default OFF):
      • enable.approvalsVisibleToReaders: true on an object lets a caller who can read one of its records see that record's approval requests and full history, read-only. It holds on the approvals API and the generic data API, on a read that names the record, with no approval action and the inbox not widened.
      • The flag is read from the live registry on every call. An object registered after boot takes effect, and removing the flag turns the tier off without a restart. The host's constructor option still works alone.
      • The Studio object form offers the row, with en / zh-CN / ja-JP / es-ES leaves.
    • Carried: the stray /build-i18n.pid outside the repository, from a dev's shell slip. Its removal was refused to the dev by the environment's safety check and is left with the maintainer.
    • Mis-close scan: the squash message carries Fixes #22560 alone, and the merge closed plugin-approvals: the ruled record-reader visibility tier (#8652) is reachable only through a constructor option no app can set — declare its opt-in where an app can #22560 alone.

    This act removes pm:dispatched; the domain, priority, target and area labels stay.


    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

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsdomain:specenhancementNew feature or requestpriority:p2Medium: important, M3target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions