Repository navigation
plugin-approvals: sys_approval_request declares its per-caller viewer block under attachedOnRead, with a conformance test against attachViewers (#22211 ruling A, plugin-approvals half) #22387
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p3·domain:services·area:workflow·pm:blockedon #22386 (findingremoved). Execution of ruling A on #22211Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T01:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-approvals(sys-approval-request.object.tsand a conformance test againstattachViewers) ⇒domain:services. Rationale: plugin-approvals belongs to that lane.- Execution of ruling
6070963704. It declaresviewer(can_act,can_override,is_submitter, allboolean) under the key spec(data):ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386 adds. The conformance test pins that the keysattachViewersemits equal the declared ones (ADR-0049: a declaration has a reader from landing day). - Blocked on spec(data):
ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386 (the key does not exist yet), as the card's own line says. - Closes plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211 too: the PR carriesFixes #22211. The pin is that the 8 shippedvisiblepredicates pass the validator, and a misspelt leaf is still refused. - Why p3: the same grade as plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211.
- Execution of ruling
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itand removed
on Oct 9, 2026 objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsNote from
domain:specseat 2 (seat post #18549) ·session_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T04:33Z. ⛔ Not a claim. It is a scope note for whoever claims this card, recorded so the card carries it.The conformance test checks keys and value types. #22386, the spec half and this card's blocker, declares each block's leaves with a value type (
number | text | boolean | date). In that card, the shared validator judges leaf names only. So under ADR-0049, the declared types' reader is this card's conformance test:- the keys
attachViewersemits equal the keyssys_approval_requestdeclares underattachedOnRead.viewer; - the runtime type of each emitted value matches its declared type (
can_act,can_overrideandis_submitter, allboolean).
The seat order on #22386 records this.
Generated by Claude Code
- the keys
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsNote from
domain:specseat 2 (seat post #18549) ·session_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T06:14Z. ⛔ Not a claim. It adds to the scope note6074324160, from the contract review6075426209(F1) on PR #22425, the spec half of this card.Two more field-existence doors for this card to settle. PR #22425 threads
ObjectSchema.attachedOnReadinto the shared build validator (@objectstack/lint→@objectstack/formula). Two other builders of therecord.*field set readfieldsonly:packages/mcp/src/mcp-http-tools.ts(describeObject().fields[].name);packages/services/service-automation/src/plugin.tssetObjectSchemaResolver(registry.getObject().fields). ItscheckFieldExistenceruns in every scope.
They reach nothing today. Once this card declares
vieweronsys_approval_request, arecord.viewer.*expression written through either door is refused there, while the build validator accepts it. When this card lands its declaration, it either threads the key into both builders, or shows that no expression onsys_approval_requestreaches them. It names which in its PR.
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsUnlock scan:
pm:blocked→pm:queue·domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T11:26Z- Blocker gone: spec(data):
ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386 (Blocked-by: #22386, triage6072656840) closed as completed when PR feat(spec): ObjectSchema.attachedOnRead declares per-caller read attachments, and the shared validator judges record.BLOCK.LEAF against them #22425 merged as9af0005d5, which is onorigin/main. - Premise re-checked on
9af0005d5:ObjectSchema.attachedOnReadexists (packages/spec/src/data/object.zod.ts, from about:1703). The shared validator reads it inpackages/lint/src/validate-expressions.ts(attachedOnReadOf) andpackages/formula/src/validate.ts.plugin-approvalsdeclares nothing under it yet.sys-approval-request.object.tsstill carries itsrecord.viewer.*predicates, andattachViewersis still called fromlistRequests(approval-service.ts:6817) andgetRequest(:6934) only.- The two builders in scope note
6075461181still readfieldsonly:packages/mcp/src/mcp-http-tools.tsandpackages/services/service-automation/src/plugin.tscarry noattachedOnRead. That note stands for the claimant.
- The direction stands as written in triage
6072656840and the twodomain:specscope notes (6074324160: the conformance test checks keys and value types;6075461181: the two field-existence doors). The PR also carriesFixes #22211.
- Blocker gone: spec(data):
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsClaim: PM loop round 2
Session:session_01WYYhVJ78u7PhwFViWo1EmQ
Account:os-elon-musk(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-22387-approval-viewer-attached-on-read
Worktree:objectstack-issue-22387
Domain:domain:services
Seat:domain:services#2(seat post #21118)
File surface, per the card body, triage6072656840and thedomain:specscope notes6074324160and6075461181, read onorigin/main9af0005d5(PR #22425 merged, #22386 closed):packages/plugins/plugin-approvals/src/sys-approval-request.object.ts: declareattachedOnRead: { viewer: { can_act: 'boolean', can_override: 'boolean', is_submitter: 'boolean' } }, in the shapeObjectSchema.attachedOnReadtakes onmain(packages/spec/src/data/object.zod.ts, about:1703).- New tests in
plugin-approvals:- a conformance test: the keys
attachViewersemits equal the declared leaves, and each emitted value's runtime type matches its declared type; - a pin:
validateStackExpressionswithrunAuthoringRules('build')accepts the 8 shipped actionvisiblepredicates; a misspelt leaf (record.viewer.can_actt) is still refused.
- a conformance test: the keys
approval-service.ts(attachViewers, about:7018): read only, as the conformance test's subject. ⛔ No change to the per-callerviewersemantics ([P3] approvals: server-computed viewer capability on getRequest (precise approver gating for declared actions) #3310).- The two field-existence doors that build
record.*fromfieldsalone (scope note6075461181):packages/services/service-automation/src/plugin.ts(setObjectSchemaResolver, about:1185), in this lane: thread the declared blocks into the schema it resolves, or show that no expression onsys_approval_requestreaches it;packages/mcp/src/mcp-http-tools.tsisdomain:cli's: ⛔ not edited here. Show whether an expression onsys_approval_requestreaches it; if it needs threading, report it and the seat files the card.- The PR names which, for each door.
.changeset/22387-*.md:patchfor@objectstack/plugin-approvals(and for@objectstack/service-automationif its resolver changes).- ⛔ No
packages/spec,lint,formula,mcporcontent/docsedit. ⛔ No other read attachment (decision_progress,pending_approver_groups, the flow steps; the ruling requires none). (Stop on breach and explain in the report.)
Container & model:M,mode:subagent,model: default—dispatch-gates --tierover the surface prints "no path-derived mandate"; one declaration, a conformance test and two door readings.
Clause-②: no - No new export and no wider accepted input on any published contract. The exported
SysApprovalRequest's type does not change:ObjectSchema.createtakes onlyfieldsfrom the literal into its return type, andattachedOnReadis the optional member spec(data):ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386 landed under its ownClause-②: yes (widening). - The validator's widening, a declared block's leaves accepted after
record., is spec(data):ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386's act. This card fills that key in for an object whose service already attachesviewerto every row it serves ([P3] approvals: server-computed viewer capability on getRequest (precise approver gating for declared actions) #3310). The object's 8 shipped predicates already read it, so the validator's refusal of them is the defect (plugin-approvals: sys_approval_request's 8 actionvisiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211). The declaration pulls the validator back to what the object ships; it does not widen a contract. - Re-graded at review against the diff: if the diff publishes anything wider, the seat amends this line and owes an at-tier record.
Responsibility:platform code: sys_approval_request's 8 shipped action visible predicates read record.viewer, which the object does not declare, so the build validator refuses the object's own predicates|#22386 (PR #22425) landed ObjectSchema.attachedOnRead and the validator's reading of it; no object declares a block under it yet|every stack that validates sys_approval_request's actions with validateStackExpressions and runAuthoringRules('build'), measured by #22032's pass-4 dev (6053764412)
Thread-read: 6079930277
Serial constraints cleared: read 2026-10-09T11:31Z: - Open PRs (14, each file list read against
plugin-approvals/**,service-automation/src/plugin.ts,mcp-http-tools.ts,lint/src/validate-expressions.tsandformula/**):- PR feat(spec): ConnectorProviderContext.resolvePackagePath, a host-provided package anchor for provider factories (#22434) #22465 (draft, spec(integration):
ConnectorProviderContextgains a host-provided package anchor, so a provider resolves an app-relative path against the app's root (#22423's spec stage) #22434) editsservice-automation/src/plugin.tsat about:183–:260(the package-root loader), region-disjoint fromsetObjectSchemaResolver(about:1185). Whichever lands second mergesmain. - The release PR chore: version packages #21988 bumps
plugin-approvals' andformula'spackage.jsonandCHANGELOG.mdonly.
- PR feat(spec): ConnectorProviderContext.resolvePackagePath, a host-provided package anchor for provider factories (#22434) #22465 (draft, spec(integration):
- In-flight claims in
domain:services:- seat 1: 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 (
plugin-security,plugin-sharing), disjoint. plugin-approvals mounts its ADR-0043 action pages (/api/v1/approvals/act) only through http.server getRawApp, so a hosted tenant kernel, which has none, answers 404 ROUTE_NOT_FOUND to every approval e-mail link #22438 (ruled A plus a sweep, split pending at seat 1) will add an act member toplugin-approvals' service and plugin; this card edits neither file. - seat 2: auth: plugin-auth doors that re-dispatch get-session or call a better-auth endpoint in-process still renew a cookie session without re-issuing its cookie (split session, the residue of #22258) #22398 (
plugin-auth), disjoint.
- seat 1: 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 (
- plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211 (domain:spec,pm:blocked) closes from this card's PR (Fixes #22211), per the card's own closing line and the ruling6070963704.
domain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T11:31Zobjectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22387,
"status": "done",
"branch": "claude/issue-22387-approval-viewer-attached-on-read",
"pr": "#22479",
"session": "session_01WYYhVJ78u7PhwFViWo1EmQ — subagent of the domain:services seat 2 PM; the dispatch's Claim 6080005767 is this run's identity (newest Claim names this branch, checked before any edit)",
"premise_still_valid": true,
"summary": "sys_approval_request now declares attachedOnRead: { viewer: { can_act, can_override, is_submitter: 'boolean' } }. The 8 shipped action visible predicates pass validateStackExpressions, runAuthoringRules('build') and the object save door's runRuntimeAuthoringRules({type:'object'}), and a misspelt leaf is still refused naming the 3 declared leaves (reproduced first on base c512c25: 8 x unknown fieldvieweronsys_approval_request; os validate exit 1 -> 0). A conformance test drives the real ApprovalService on a real ObjectQL over SqlDriver for 5 caller shapes (submitter, pending approver, platform-admin override, system, the approver on a finalized request) through getRequest and listRequests, and holds the served viewer keys and each value's runtime type to SysApprovalRequest.attachedOnRead.viewer read from the object; attachViewers emits exactly the 3 keys, all boolean, no extra key (assumption 3 holds). Doors (assumption 5, measured): the MCP validate_expression tool does NOT reach sys_approval_request in any shipped composition (sys_ guard, allowSystemObjects never set) -- not threaded, not edited; service-automation's flow-registration resolver DOES reach it (any flow whose start objectName is sys_approval_request), but a flow's record is the stored row and never carries viewer (hook row and engine.find row measured without it; the flow run failsNo such key: viewer), so its refusal is the true verdict and the block was deliberately NOT threaded -- neither of scope note 6075461181's two options literally; the build-side over-acceptance this leaves is finding F1 / open question 1 for domain:spec. Assumption 4: the resolved exported type is unchanged (Pick of fields); the emitted .d.ts text gains 7 lines inside the discarded Pick argument.",
"tests": "All at HEAD 506350e. (1) pnpm --filter @objectstack/plugin-approvals exec vitest run --maxWorkers=2 -> Test Files 64 passed (64), Tests 916 passed (916), os-verify-lock VERDICT command-exit 0. (2) pnpm --filter @objectstack/plugin-approvals typecheck -> VERDICT command-exit 0; check:test-typecheck: OK ... 8 file(s) / 324 error(s) / 27 pinned signature(s) held (ledger unchanged; the 2 new test files compile with 0 errors). (3) Builds: pnpm --filter '@objectstack/plugin-approvals^...' --filter '@objectstack/lint...' build, pnpm --filter @objectstack/plugin-approvals build, turbo run build --filter=!@objectstack/docs (72 tasks, 71 cached) -> all VERDICT command-exit 0. (4) eslint (same binary and --no-inline-config as pnpm lint) on the 4 lintable changed .ts files: --format json files 4 errors 0 warnings 0; package.json, pnpm-lock.yaml and the changeset answer 'File ignored because no matching configuration was supplied'; eslint.config.mjs enables no type-aware linting and no import/ cross-file rules, so untouched files' verdicts cannot move. (5) os validate (built CLI) on a scratch defineStack config with the object + a record-change flow reading record.viewer.can_act: object without attachedOnRead -> exit 1, 'Author-time rules failed (9 issues)' (8 action visible + the flow condition); this branch -> exit 0 'Validation passed'. Ablations, all via node scripts/ablation-replace.mjs wrap mode under os-verify-lock, mutation proven by anchor count + blob change, restore proven by blob == HEAD + empty git diff HEAD: A1 delete the attachedOnRead block from sys-approval-request.object.ts (anchor 1 -> 0, blob c4b45c28b39d -> fc171e176107): 9 of 11 red -- pin 3/4 red ('every predicate passes': 8 x unknown fieldvieweronsys_approval_requestvs []; misspelt-leaf: expected length 1 got 8; population: 0 declared leaves), control case green as designed; conformance 6/7 red (declares-the-block + emitted keys on all 5 shapes; both-values case vacuously green, held by the declares-the-block case); restored blob c4b45c28b39d == HEAD. A2 attachViewers serves can_act as heldSlot(...) without !== undefined (blob 9a1822845a2f -> 86ee0cb9a41a): conformance 4/7 red ('a current pending approver via getRequest:can_actdeclared boolean, served "u_app"'; 'can_act across the battery: expected [ false, u_app, undefined ]'); restored == HEAD. A3 attachViewers also emits can_comment: true: conformance 5/7 red ('emitted keys: expected [ can_act, can_comment, ... ] to deeply equal [ can_act, can_override, ... ]' on every shape); restored == HEAD. A3's first attempt was refused by ablation-replace before measuring (replacement contained the anchor, anchor count 1 -> 1), file restored to HEAD, rerun with a non-overlapping replacement. Declared-type check: tsc --emitDeclarationOnly of the object file with and without the block differs by exactly the 7-line attachedOnRead literal, inside the Pick argument (emitted lines 1017-4776).",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 506350e: 77 families derived, 77 run with exit codes recorded, 0 NOT-MEASURED (--ran reconciliation: '77 derived famil(ies) accounted for -- 77 run, 0 NOT-MEASURED (a DERIVED zero -- all 77 recorded an exit code and none of them is 3)'). All exit 0; check:i18n and check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET, no dist) and exited 0 on rerun after the build (check-i18n-bundles: OK (9 package(s) -- all bundles in sync)). Notables: check:test-source-alias OK, check:engine-double-contract 0 (no engine double added), check:workspace-manifest-cycles OK (517 edges, no cycle), check:type-check-debt OK, check:nul-bytes OK. Remote CI: not read, not awaited (in_progress at report time is the honest value).",
"line_budget": "392 changed lines (+391 / -1) over 7 files vs the 5000 human-merge threshold: under. No governed surface (.claude/, skills/, AGENTS.md, docs/adr/**) touched, so no skills line ratchet applies.",
"files_changed": [
".changeset/22387-plugin-approvals-viewer-attached-on-read.md (new, patch @objectstack/plugin-approvals)",
"packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (the declaration + comment; the action design note points at it)",
"packages/plugins/plugin-approvals/src/sys-approval-request-viewer.conformance.test.ts (new)",
"packages/plugins/plugin-approvals/src/sys-approval-request-attached-on-read.test.ts (new)",
"packages/plugins/plugin-approvals/package.json (devDependency @objectstack/lint)",
"packages/plugins/plugin-approvals/vitest.config.ts (anchored alias @objectstack/lint -> lint/src/index.ts)",
"pnpm-lock.yaml (the importer link, +3 lines)"
],
"deviations": [
"File surface widened beyond the dispatch's list by package.json + vitest.config.ts + pnpm-lock.yaml in plugin-approvals: the validator pin imports @objectstack/lint, which plugin-approvals did not depend on, and check:test-source-alias requires the source alias (ledger is shrink-only). Precedent: packages/platform-objects carries the same devDependency and alias.",
"service-automation door: neither 'thread' nor 'not reached'. It is reached, and threading was deliberately not done because the flow binding never carries viewer (measured); no service-automation edit, so no service-automation changeset.",
"The validator pin also judges the object save door (runRuntimeAuthoringRules type object), in addition to the build pair the dispatch named.",
"Commit trailers are the model-free pair AGENTS.md prescribes, not the harness reminder's model-named Co-Authored-By; the PR body ends with AGENTS.md's session-URL footer, not the harness's form.",
"Branch not merged with origin/main (4 commits ahead at report time: rest, auth, fleet-write); none touches this diff's packages, so the gate union stands at 506350e.",
"Gate battery ran under nohup in the background, waited on in the foreground with tail --pid (the check:* posture the role file prescribes), not under the verify lock."
],
"mcp_calls": "0 -- no MCP tool called (reads went through gh api REST; writes through scripts/pm relay tools).",
"api_writes": "3 REST writes, each a fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#22479, draft, body read back identical 16406 bytes); (2) label-write --assign -> POST /repos//issues/22479/assignees (os-elon-musk, read back); (3) post-stamped --comment=22387 -> POST /repos//issues/22387/comments (this report). Plus git push x4 (empty-branch probe, then 3 commits) -- not REST.",
"open_questions": [
{
"question": "F1 -- with attachedOnRead.viewer declared, the shared build validator (lint's buildFieldIndex / buildAttachedOnReadIndex) accepts record.viewer.* at EVERY site bound to sys_approval_request, including a flow's start condition; os validate on a record-change flow over this object with condition record.viewer.can_act == true now answers 'Validation passed' (exit 0), while that flow's record is the stored row (no viewer, measured) and the run fails 'No such key: viewer'; service-automation's registration door still warns unknown field. Scope note 6075461181 framed the fix as threading the block into the flow door; measurement says the build side is the one that is wrong for flows. Should attachedOnRead be judged only where the bound row is a served row?",
"options": [
"A -- lint applies a declared block only at object action predicates (visible / disabled), the one surface whose binding is the row the declaring service serves; flow conditions, field formulas and validation rules keep the fields-only verdict. Business need: the only real reader is the 8 shipped predicates; no flow in the repo reads record.viewer. Long-term: matches the key's own contract text ('blocks a service attaches to each row it serves'). AI-error: os validate refuses a flow condition that faults every run instead of green-lighting it. Scope: a narrowing inside the existing expression rule, no new gate or code; needs a lint card (domain:spec) and lands before #22386 and this changeset release.",
"B -- keep every surface (the ruling's literal 'field-existence set'), accept the build/flow-door disagreement, document it. Cheapest now; leaves a trap os validate cannot see, on a key AI authors will copy.",
"C -- thread the block into service-automation's resolver (the scope note's option). Rejected by measurement: both doors would then accept a condition that fails every run."
],
"recommendation": "A, on all four axes: real usage is action predicates only; it is the contract-first reading of the key's own text; it makes the AI-authored flow error loud at os validate; it is a narrowing with no new gate. Nothing has shipped yet (#22386's and this card's changesets are pending), so no transition is owed."
}
],
"out_of_scope_findings": [
"class: c · reach: public door -- os validate (built CLI, this branch) answers 'Validation passed' exit 0 for a defineStack config holding sys_approval_request + a record-change flow whose start condition is record.viewer.can_act == true; executing that flow over the row a record-change flow binds fails 'condition failed to evaluate as CEL: No such key: viewer'; control (object without attachedOnRead) -> exit 1 naming the condition · evidence: packages/lint/src/validate-expressions.ts buildFieldIndex + buildAttachedOnReadIndex feed every object-bound site in runStackExpressionPasses, flows included; ApprovalService.attachViewers (approval-service.ts:7018) is the only producer of viewer, called from listRequests/getRequest only; the afterUpdate hook row (record-change trigger's ctx.result) and an engine.find row carry no viewer (measured). Seam: spec:ObjectSchema.attachedOnRead -> runtime:packages/lint/src/validate-expressions.ts#runStackExpressionPasses (flow start condition) | consumer: service-automation AutomationEngine.evaluateCondition over the stored row. Unreleased (pending changesets 22386-* and 22387-). Owner lane: domain:spec (#22386). Same as open question 1 · dedupe words: attachedOnRead flow condition record.viewer accepted os validate · read attachment block judged on flow surface stored row · sys_approval_request flow start condition viewer No such key",
"carrier: 承接者:无 (packages/mcp is domain:cli's) · noted, not filed -- MCP validate_expression builds record. from describeObject().fields only; it is unreachable for sys_approval_request in every shipped composition (runtime HTTP door passes only grantedScopes, packages/runtime/src/domains/mcp.ts:134; stdio plugin passes no options, packages/mcp/src/plugin.ts:656), measured through MCPServerRuntime.handleHttpRequest: 'Object "sys_approval_request" is a system object and is not exposed via MCP'; with allowSystemObjects: true it answers unknown fieldviewer(unknown-field) at the validation site. In PR Acceptance notes F2.",
"carrier: domain:spec seat (owner of #22386) · noted, not filed -- prose that goes stale when this lands: packages/spec/liveness/object.json attachedOnRead note ('a declaration awaiting its named reader' until this test lands) and packages/lint/src/authoring-rules.ts #22032 pass-4 comment ('the build refuses 8 ... the producer fix is #22211'). In PR Acceptance notes.",
"carrier: 承接者:无 · noted, not filed -- sys_approval_request carries 21 pre-existing build warnings, unchanged by this PR: 1 title-format-retired and 20 field-group-undeclared (fields declare group System/Target/State, the object declares no fieldGroups). Not reproduced in a UI; observation only. In PR Acceptance notes."
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsACCEPT — PR #22479 at
506350e9, pending CIdomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· read on GitHub 2026-10-09T12:34ZChecked on GitHub and in the diff, not from the report:
- Shape: draft, base
main, assigneeos-elon-musk, 7 files, +391 / −1.- Line 1
Fixes #22387, line 2Clause-②: no. - Line 3 now reads
Part of #22211, after the seat's edit (see "The closing-target red" below).
- Line 1
- The declaration, as ruled (
6070963704):attachedOnRead: { viewer: { can_act: 'boolean', can_override: 'boolean', is_submitter: 'boolean' } }onsys_approval_request, with a comment naming its two readers. The action design note points at it.approval-service.tsis untouched, so the per-callerviewersemantics ([P3] approvals: server-computed viewer capability on getRequest (precise approver gating for declared actions) #3310) are unchanged. - Pins:
sys-approval-request-attached-on-read.test.ts:- the population case;
- every
viewer-reading predicate passes the build pair and the object save door; - a misspelt leaf is refused once at all three, naming the declared leaves;
- control: without the declaration, every reader is refused again.
sys-approval-request-viewer.conformance.test.ts:- drives the real
ApprovalServiceon ObjectQL overSqlDriver, for 5 caller shapes throughgetRequestandlistRequests; - holds the served keys and each value's runtime type to the declaration read from the object;
- a case proves both values of every leaf are observed.
- drives the real
- Ablations A1 to A3 (block removed;
can_actserved as a non-boolean; an extra emitted key) each red the cases they should, and each was restored to a blob equal to HEAD.
- File surface:
- Beyond the claim's list:
plugin-approvals'package.json(@objectstack/lintas a devDependency only),vitest.config.ts(the anchored source aliascheck:test-source-aliasrequires) and 3 lines ofpnpm-lock.yaml. The validator pin needs these to import@objectstack/lint, andplatform-objectsalready has the same entry. Publishedfilesand runtime dependencies are unchanged. Accepted. - No
service-automationorpackages/mcpedit (see the doors below).
- Beyond the claim's list:
- The two field-existence doors (scope note
6075461181), measured, and the PR names which:- MCP
validate_expression: not reached in any shipped composition (the system-object guard). Not threaded, and not this lane's file. service-automation's flow resolver: reached, but a flow'srecordis the stored row and never carriesviewer(the hook row and anengine.findrow were measured), so its refusal is the true verdict. Threading it would make both doors accept a condition that fails on every run. Not threading it is accepted.
- MCP
Clause-②: no, re-graded against the diff:- The resolved exported
SysApprovalRequesttype is unchanged:ObjectSchema.createkeeps onlyfieldsfrom the literal, and the 7 extra.d.tslines sit inside the literal it discards. - The served object definition gains a key whose schema spec(data):
ObjectSchema.attachedOnRead— an object declares the blocks a service attaches per caller on read, and the validator judgesrecord.<block>.<leaf>against it (#22211 ruling A, spec half) #22386 published under its ownClause-②: yes. - No
packages/specand no governed rule text are touched, so no at-tier record is owed.
- The resolved exported
- Changeset, sentence by sentence against the diff:
patchfor@objectstack/plugin-approvals.- The eight
visiblepredicates read the three leaves: true. vieweris served onlistRequests/getRequestfrom the caller: true, and the conformance test drives both reads.- The build pair and the save door refused all eight: the control case reproduces it.
- "Unchanged: who sees which decision button": the service is untouched.
- The data door, a record-change flow's
recordand a flow action's subject row do not carryviewer: measured. - "The exported
SysApprovalRequesttype resolves to the same type": true, as above.
- The eight
- Gates: 77 derived, 77 run, all exit 0 at
506350e9. The branch is 4 commits behindmain, in other packages; CI reads the merge ref, and the queue builds onmain.
The closing-target red (
The card this PR closes must claim this branch):Fixes #22211was refused because plugin-approvals: sys_approval_request's 8 actionvisiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211's governingClaim:isdomain:specseat 3's (6062089126), and an execution seat claims only in its own lane.- The seat took the gate's third remedy,
Part of #22211, which a body edit re-runs with no push. - plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211 closes at landing by this seat, ondomain:specseat 2's instruction "It closes when plugin-approvals:sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387's PR lands." (6072125030).
Out-of-scope findings:
- class c, the dev's
open_questions[0](F1) → filed as lint: a declaredattachedOnReadblock is accepted in a flow condition on its object, where the bound row is the stored row and never carries it (os validatepasses, the flow fails at run time) #22481 for triage (domain:specby its file).- A declared block is accepted in a flow condition, whose row never carries it:
os validatepasses, and the flow fails on every run. - The dev recommends A (apply a block only at action predicates). It narrows reader (1) of ruling
6070963704, so it is not decided here, and it does not hold this PR: today the build refuses the object's own shipped predicates, and no flow in the repo readsrecord.viewer.
- A declared block is accepted in a flow condition, whose row never carries it:
carrier: none, the MCP door withallowSystemObjects: true(F2) → Acceptance notes, not filed.carrier: domain:spec seat, the two pieces of stale prose inliveness/object.jsonandlint/src/authoring-rules.ts→ carried in lint: a declaredattachedOnReadblock is accepted in a flow condition on its object, where the bound row is the stored row and never carries it (os validatepasses, the flow fails at run time) #22481's body.carrier: none, the 21 pre-existing build warnings on the object → Acceptance notes, not filed.
Owed before landing: every check green on
506350e9.At landing:
Fixes #22387closes this card.- The seat closes plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211 with the provenance above, then clearspm:dispatchedand the assignee here.
- Shape: draft, base
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22479 →
3403be84c, a single-parent queue squash; this card closescompleteddomain:servicesseat 2 ·session_01WYYhVJ78u7PhwFViWo1EmQ· 2026-10-09T14:07Z- Landing shape:
3403be84chas one parent and is an ancestor oforigin/main. It merged 2026-10-09T13:37Z on its first queue entry. 7 files, +391 / −1, the reviewed net diff.Fixes #22387closed this card. - Content on
origin/main:sys_approval_requestdeclaresattachedOnRead.viewer(can_act,can_override,is_submitter, allboolean), so the shared validator accepts the object's own eight actionvisiblepredicates.- The conformance test holds
attachViewers' served keys and value types to that declaration.
- Review of record: ACCEPT
6080987407.Clause-②: no, re-graded against the diff: the exported type is unchanged and no published contract changed, so no at-tier record was owed. - plugin-approvals: sys_approval_request's 8 action
visiblepredicates readrecord.viewer, a block the service attaches on read, and the shared expression validator refuses all 8 as an undeclared field #22211: the triage seat closed it at 2026-10-09T14:07Z on its own plan line (6072125030). This PR names it asPart of. - Follow-up: lint: a declared
attachedOnReadblock is accepted in a flow condition on its object, where the bound row is the stored row and never carries it (os validatepasses, the flow fails at run time) #22481 (F1: a declared block is accepted in a flow condition whose row never carries it), with triage,domain:specby its file. pm:dispatchedand the assignee are cleared in this stroke.
- Landing shape:
Filing gate: ③ a maintainer-directed task. This is the plugin-approvals build card that the ruling
6070963704on #22211 names: "plugin-approvals (the declaration and the conformance test)". Filed by thedomain:specseat 2 (seat post #18549,session_01DhTqaEHqPVSVnAkjG3jywn). ⛔ Not graded or routed here; ⛔ not a claim.Blocked-by: #22386
What this card builds (quoted from the ruling)
sys_approval_requestdeclaresviewerwithcan_act,can_overrideandis_submitter, allboolean", under the object-level key that the spec card adds (working nameattachedOnRead).attachViewersemits equal the keyssys_approval_requestdeclares" (ADR-0049: a declaration has a reader from landing day).validateStackExpressionstogether withrunAuthoringRules('build')refuses the object's 8 actionvisiblepredicates today, withunknown field `viewer` on `sys_approval_request`(measured by finding(lint): the object save door gives no build verdict on validation conditions, field-rule slots (requiredWhen etc.), option visibleWhen or action predicates; os build refuses them, a metadata save stores them (#22019's sibling) #22032's pass-4 dev,6053764412). They then pass, and a misspelt leaf is still refused.@objectstack/plugin-approvalspatch.Where (at
origin/main, read at filing)packages/plugins/plugin-approvals/src/sys-approval-request.object.ts. The 8 predicates readrecord.viewer.*, and the design note sits at about:388–:418.packages/plugins/plugin-approvals/src/approval-service.ts.attachViewersis at about:7018, called fromlistRequests(:6817) andgetRequest(:6934) only.⛔ Not this card
decision_progress,pending_approver_groups, the flow steps). The ruling: they "may be declared under the same key when a predicate first reads them; this ruling requires none of them".viewersemantics ([P3] approvals: server-computed viewer capability on getRequest (precise approver gating for declared actions) #3310, settled).Closing
This card's PR carries
Fixes #22211beside its ownFixesline. The defect closes when the 8 shipped predicates pass the validator.Generated by Claude Code