Repository navigation
spec(data): ObjectSchema.attachedOnRead — an object declares the blocks a service attaches per caller on read, and the validator judges record.<block>.<leaf> against it (#22211 ruling A, spec half) #22386
Description
Activity
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p3·domain:spec·area:workflow·pm:queue(findingremoved). Execution of ruling A on #22211Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-09T01:53Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/spec(the newObjectSchemakey and its liveness-ledger row) and the shared validator inpackages/lint⇒domain:spec.packages/formulais a declared cross-lane path intodomain:engine, as the card says.- Execution, not a decision. The shape is the maintainer's ruling
6070963704(batch 🔗 Broken links detected in documentation #295 item 1, A). The final key name is this card's to settle, as the ruling allows. Clause-②: yes (widening). That makes it spec-lane work, and the PR owes the contract-tier review.- Why p3: the same grade as the defect it 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). Eight shipped approval predicates are refused by the validator today. Nothing breaks at runtime. - Serial:
validate-expressions.tsis in flight on lint: a select option'svisibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274 and PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315. Cut after them, or rebase on whichever lands first. - plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387, the plugin-approvals half, is blocked on this card.
- Execution, not a decision. The shape is the maintainer's 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 actionsClaim: PM loop round 1 (the spec half of #22211's ruling A
6070963704, graded by triage6072644877) · 2026-10-09T02:49Z
Session:session_01DhTqaEHqPVSVnAkjG3jywn
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22386-attached-on-read
Worktree:objectstack-issue-22386
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface (atorigin/main11d119ab1or later; stop on breach and explain in the report):packages/spec/src/data/object.zod.ts.ObjectSchemagains one optional key, with the working nameattachedOnRead. It is a strict record from block name to that block's leaf keys and their types: the blocks a service attaches to the rows it serves, computed per caller and never stored. The final name is this card's to settle, and the report says why.- ⛔ It is not a field. No driver, form, list view, export, write path or translation bundle reads it.
- The surface also covers the spec's generated artifacts, through the repo's generators only, and its liveness-ledger row (
packages/spec/liveness/object.json).
- The shared validator: reader 1, from landing day (ADR-0049).
- In
packages/lint/src/validate-expressions.ts,buildFieldIndexadds each declared block name to the field-existence set. - The second segment of
record.<block>.<leaf>is judged against the block's declared keys, under the existingunknown-fieldrule, inpackages/formula/src/validate.ts(RECORD_REF_RE,checkFieldExistence) and/orpackages/lint, wherever the measurement places it. Measurepackages/lint/src/validate-predicate-path-refs.ts(Publish-time validation of predicate path references — a spec-delivered predicate naming a nonexistent path should fail at authoring, not evaluate to a guess at render (#6936 companion) #7010,:55–:63) before choosing. - ⛔ No new rule, no new refusal code, and no exception for
viewer(triage6054592515; the ruling's "⛔ Not ruled"). packages/formulaisdomain:engine. It is declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act.
- In
- Pins: a declared leaf is accepted; a misspelt leaf (
record.viewer.can_actt) is refused; an undeclared block is refused as today; and an object without the key behaves as today. - Changesets:
.changeset/22386-*.md, with@objectstack/specminorand@objectstack/lint/@objectstack/formulapatchwhere their files move. - ⛔ Not
sys_approval_request's declaration and not the conformance test. Those are plugin-approvals:sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 (domain:services), blocked on this card.
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate). A contract review atCONTRACT_REVIEW_TIERis owed before enqueue.
Clause-②: yes (widening: a new optionalObjectSchemakey)
Responsibility:packages/lint's field index (buildFieldIndex) knows onlyfieldsand the injected columns, so the 8 shippedsys_approval_requestaction predicates onrecord.viewer.*are refused | none: no shape that the validator reads can declare a per-caller block today (seat 3's measurement,6062299739) | everyos build/os validateof a stack that carries plugin-approvals, and the object save door once #22032's pass 4 lifts its fence
Thread-read: 6072644877
Serial constraints cleared:packages/lint/src/validate-expressions.tsis also edited by PR fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads an unbound ctx/os member (#22274) #22392 (lint: a select option'svisibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274,domain:specseat 1, draft, the select-optionvisibleWhenpass) and by PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 ([v18] flow text slots: read ADR-0032 §3's{{ }}delimiter instead of single-brace{token}(notify title/message and the other flow string slots), converting only what renders the same #22110, this seat, held behind finding(ci): the shard-timings dataset rests on ONE scheduled run, and records @objectstack/spec at 1134.86 s against 1573–1651 s executed — #16468's 25%-headroom ceilings built on it would red every PR that runs spec #22014). This card editsbuildFieldIndexand the record-reference judgement, not those passes.- Building runs in parallel; landing is serial. Whichever of those PRs lands first, this branch merges
mainbefore its review head. If either is in the queue when this PR is ready, this PR goes after it. - No open PR touches
object.zod.ts,packages/formula/src/validate.tsorliveness/object.json; all open PRs' file lists were read at this stamp.
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22386,
"status": "needs_decision",
"branch": "claude/issue-22386-attached-on-read",
"pr": null,
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's session (this run is a subagent of it)",
"premise_still_valid": true,
"summary": "Stopped on the claim's stop line: the change needs FIVE files outside the claim 6073245703's surface, so no PR is opened. Premises all hold on origin/main 11d119a (buildFieldIndex :152/:174 = fields + injectedColumnsFor; RECORD_REF_RE :312 captures one segment and checkFieldExistence :625 refuses the first unknown one; attachedOnRead: 0 hits, git grep -c exit 1, control injectedColumnsFor 3 files). Everything inside the surface is built and pushed on the branch (head 5fd4cfd, origin/main e75dced merged by os-regen-merge.sh after #22392 landed on validate-expressions.ts; that PR's three symbols verified present): (1) ObjectSchema.attachedOnRead, beside fields: block name → (leaf key → value type), both key levels in the field-name grammar, leaf type = FieldType.extract(number|text|boolean|date), i.e. the four Field.returnType value types; describe says what it is and is not; liveness row object.json live (authoring consumer, the action.execution precedent) with producer + verifiedAt; generated artifacts by generators only (authorable-surface/data.json via build, liveness state-counts via gen:liveness-counts, content/docs/references via gen:docs; check:generated 15/15 current at 5fd4cfd). (2) Reader: lint buildFieldIndex adds each declared block name to the field-existence set (attachedOnReadOf/buildAttachedOnReadIndex) and threads block → leaves into the shared validator at both call sites that pass the field index; formula checkFieldExistence judges the SECOND segment of record.BLOCK.LEAF (and previous.) via a new optional ExprSchemaHint.attachedOnRead and checkAttachedLeaf, under the EXISTING unknown-field code, field = dotted path, suggestion = nearest leaf, message names the declared leaves; no new rule id, no new code, nothing keyed on viewer. The head scan RECORD_REF_RE is untouched (second segment read with a separate sticky regex), so an object without the key takes exactly today's code path. Placement measured: validate-predicate-path-refs.ts:55-:63 (#7010) scopes itself to the data.* layer and leaves record.* to the sibling rules, so the judgement there would be a new rule id; it lives in formula checkFieldExistence, the existing unknown-field site. Name kept: attachedOnRead — the producer's own verb is attach (attachViewers / attachDecisionProgress / attachFlowSteps in approval-service.ts), OnRead follows the spec's enforceOnRead, 0 collisions repo-wide; readAttachments would read as file attachments, virtual/computed would read as fields. Grep proof that no driver/form/list view/export/write path/translation bundle reads it (git grep -c attachedOnRead at the branch): hits only in packages/spec (schema, its test, ledger row, authorable-surface/data.json), packages/lint/src/validate-expressions*.ts, packages/formula/src/validate*.ts, the generated reference docs and the two changesets.",
"tests": "All runs through os-verify-lock (VERDICT command-exit 0 each); shared-box seconds. Post-merge head 5fd4cfd (run m5): lint vitest 129/129 files, 5886 tests passed (includes the new validate-expressions.attached-on-read.test.ts and the #5017 meta-guard in validate-expressions.test.ts updated, treated as in-surface because it is the claimed file's own source-scan pin: obj reads gain attachedOnRead, attachedOnReadIndex joins PLUMBING); formula vitest 45/45 files, 1277 passed (includes the new validate-attached-on-read.test.ts, 9 pins); spec files: check-liveness.test.ts, object-attached-on-read.test.ts (10 pins), object.test.ts green; 12 tests red in exactly the three out-of-surface spec files named in open_questions[0] (metadata-form-zod-reconciliation 1, compose-stacks-merge-collection-refusal 9, compose-stacks-collection-pipe-arm 2). At 37711ce (m2/m3): formula typecheck 0, lint typecheck 0, spec typecheck 0 (incl. check:test-typecheck). At cb641d6 (m1): spec unit tier --project local 623/627 files, 35 failed tests: 23 in check-liveness.test.ts from the stale liveness count shard (regenerated in-surface at dd01520; green since) + the 12 above; metadata-core vitest 16/17 files, 1 red = object-schema-fls-references.test.ts 'classifies every top-level ObjectSchema key' (out-of-surface file 1). m1's lint run is VOID (dependency closure unbuilt, sdui-parser dist missing), superseded by m2/m5 after pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build. Ablation (m3, 37711ce, formula imports ./validate from src so no dist leg): node scripts/ablation-replace.mjs on packages/formula/src/validate.ts anchor 'if (leaves.includes(leaf)) return;' → 'return; /* ABLATION-22386 */': anchor x1 → x0, blob f7e24675 → ac2f0590; validate-attached-on-read.test.ts 3 failed | 6 passed (the misspelt-leaf, no-near-leaf and previous-root pins red; accept and control pins green, the expected direction); restore: blob == HEAD f7e24675, git diff HEAD empty, marker count 0; restored run 9/9 passed. Lock-free gates: check:generated exit 0 at 5fd4cfd (exit 1 at dd01520 on check:docs, regenerated by gen:docs); at 37711ce: spec check:liveness 0, check:docs 0, check:empty-state 0, check:strictness-ledger 0; root check:nul-bytes 0, check:doc-authoring 0, check-spec-docblock-symbol-anchors 0. NOT MEASURED: the full derived gate list (dispatch-gates --commands at cb641d6 printed 83 commands) — reason: stopped at the stop line before a final head; it is owed at the head that follows the decision. NOT MEASURED: metadata-protocol protocol.read-decorations / protocol.container-issue-descent — reason: closure unbuilt (entry for @objectstack/metadata-core unresolvable); by reading, neither enumerates ObjectSchema keys in a way a new optional key moves.",
"mcp_calls": "0",
"api_writes": "1 — POST issues/22386/comments (this report, via scripts/pm/post-stamped.mjs). git push is not a REST write. No PR, no label write.",
"open_questions": [
{
"question": "Widen the claim's file surface? The change needs five files outside 6073245703, measured at 5fd4cfd/cb641d6c3: (1) packages/metadata-core/src/object-schema-fls-references.ts (domain:engine) — ADR-0106 D1 OBJECT_REFERENCE_POSITIONS must classify every top-level ObjectSchema key; the row is attachedOnRead: keep (its leaves are not field names); red without it: object-schema-fls-references.test.ts:614. (2) packages/spec/src/system/metadata-form-zod-reconciliation.test.ts — every top-level key is offered by the object form or excused by a root ledger row; the ruling says no form reads it, so one ROOT_PATH omit row with its reason. (3) packages/spec/src/compose-stacks-merge-collection-refusal.test.ts and (4) packages/spec/src/compose-stacks-collection-pipe-arm.test.ts — composeStacks objectConflict:'merge' derives its not-merged collection set from the ObjectSchema shape walk, so attachedOnRead joins it with no source change, and these two pin the literal list (one-line diffs; 9 + 2 tests). (5) packages/formula/src/expression-refusal.ts (domain:engine; the #6367 declaration 6073252075 names only validate.ts) — the refusal names the declared leaves, so the unknown-field params need optional block + leaves, present together exactly when the message carries that clause (the module's own contract); plus one pin case in expression-refusal-codes.test.ts. Nothing is red without (5) — TypeScript does not excess-check spread params — which is why it is a contract gap rather than a test.",
"options": [
"A — widen by all five: four one-line classification/pin rows plus two optional params and one pin case; amend the #6367 declaration to add object-schema-fls-references.ts and expression-refusal.ts. About 30 changed lines on top of the branch; the branch then needs only the full derived gate list, the PR and the report.",
"B — widen by (1)-(4) only and drop the leaves clause: the second-segment refusal carries field + suggestion (the nearest declared leaf) only, with the first-segment template unchanged; no formula type change and no second engine-lane file. Cost: the remedy names one leaf, and none when no leaf is near (record.viewer.approved), which falls short of 'naming the declared leaves'."
],
"recommendation": "A, because (1)-(4) are mechanical consequences of adding any top-level ObjectSchema key — each is a closed-world pin that exists to force exactly this visit, so no spelling of the ruled key avoids them — and (5) is the only way the refusal can name the declared leaves without breaking expression-refusal.ts's own params-equal-clauses contract. The dispatch carried no four-axis framework, so the options carry cost and a recommendation only; if this goes to the maintainer, the axes come from the PM's copy."
},
{
"question": "Two generic, non-validator surfaces touch the key without reading its meaning; is that inside the stop line 'the key would have to be read by any non-validator surface'? (a) the ADR-0106 FLS masker would classify it keep (pass through untouched); (b) composeStacks objectConflict:'merge' refuses two stacks declaring different attachedOnRead for one object, as it does for every object collection (derived from the shape walk, no code change).",
"options": [
"A — both acceptable: neither reads what a block or leaf means.",
"B — exclude attachedOnRead from the composeStacks collection walk (a stack.zod.ts source change, another out-of-surface file)."
],
"recommendation": "A, because (a) is the classification that keeps the served metadata unchanged and (b) is the same no-silent-drop guarantee every other object collection gets."
},
{
"question": "Design choices made inside the surface that the contract review may want to strike: (i) two parse refusals beyond the card's pins, in the object's existing superRefine (no new check node): a block that names no leaf, and a block that repeats a declared field name ('It is not a field'); (ii) leaf type vocabulary = FieldType.extract(number, text, boolean, date), the Field.returnType four, so decision_progress-style object or array leaves would need a widening later; (iii) leaf TYPES have no reader in this card (the validator judges names) — the liveness note names #22387's conformance test as the second reader of the keys; (iv) @objectstack/formula gains an optional public ExprSchemaHint member (and under option A two optional params) graded patch per the dispatch, while the PR-level Clause-② is carried by spec's minor.",
"options": [
"A — keep all four",
"B — strike (i) and leave empty or colliding blocks to the validator"
],
"recommendation": "A, because (i) makes 'not a field' structural at the one door every authoring path parses through, and the other three are recorded rather than decided silently."
}
],
"out_of_scope_findings": [
"carrier: #22387 (the plugin-approvals declaration) · noted, not filed — boundary of the ruling's literal 'field-existence set': a declared block resolves at every surface the lint field index serves (validation rules, hooks, field formulas, flow conditions on that object), although only rows the declaring service serves carry it; no producer writes such a predicate today, so it is reach-less and not a card. Dedupe words: attachedOnRead served-row surfaces · read attachment validation rule formula · record.viewer write path predicate."
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsSeat order on the os-dev report 6074289657 (branch head
5fd4cfde7): A, A, A. The claim widens by five named files, then the PRdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T04:32Z · holder of claim6073245703. Thread-read: 6074289657 (the os-dev report).All three questions are execution parameters of the ruling
6070963704(A on #22211). None of them changes the ruled shape, so the seat answers them here.Q1, the five files outside the claim: A, widen by all five. This order amends the claim
6073245703.- Files (1)–(4) are mechanical consequences of adding any top-level
ObjectSchemakey. Each is a closed-world pin that exists to force exactly this visit:packages/metadata-core/src/object-schema-fls-references.ts: oneOBJECT_REFERENCE_POSITIONSrow,attachedOnRead: keep. Its leaves are not field names.packages/spec/src/system/metadata-form-zod-reconciliation.test.ts: one root ledger omit row, with the reason that the ruling says no form reads the key.packages/spec/src/compose-stacks-merge-collection-refusal.test.tsandcompose-stacks-collection-pipe-arm.test.ts: the literal collection list gains the key.
- File (5),
packages/formula/src/expression-refusal.ts(the optionalblock+leavesparams, plus one case inexpression-refusal-codes.test.ts), is how the refusal names the declared leaves without breaking that module's own rule that params match clauses. domain:enginefiles (1) and (5) are declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act. Nothing else widens.
Q2, the generic surfaces: A, both are acceptable.
- The ADR-0106 FLS masker classifies the key
keep, which leaves the served metadata unchanged. composeStacksobjectConflict: 'merge'refuses two different declarations for one object, the same no-silent-drop guarantee every object collection gets.- Neither reads what a block or a leaf means, so neither is a "non-validator reader" in the stop line's sense. ⛔ No
stack.zod.tschange.
Q3, the four design choices: A, keep all four, with two settings.
- (i) The two parse refusals in the existing
superRefinestay: an empty block, and a block named like a declared field. They make "it is not a field" structural at the one door every authoring path parses through. - (ii) The leaf type vocabulary stays
number | text | boolean | date, theField.returnTypefour. The ruling's "their types" is met, and a wider leaf type is a later widening with its own pull. - (iii) Types need a reader (ADR-0049). In this card, the validator judges names only. plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387's conformance test checks that the keys and the value typesattachViewersemits equal the declaration (viewer: threeboolean). The seat records this on plugin-approvals:sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 in this act. Your liveness note names that test as the types' reader. - (iv)
@objectstack/formulagains optional public members (ExprSchemaHint.attachedOnRead, and the two refusal params). An additive public member is a feature, so its changeset isminor, notpatch; the contract review judges the level.@objectstack/metadata-coreispatchif its published file moves.
The out-of-scope finding (a declared block resolves on every surface the lint field index serves on that object) → Acceptance notes. It reaches nothing today: no producer writes such a predicate. The carrier is #22387.
This round: add the five files; set the formula changeset to
minor; run the full derived gate list at the final head (dispatch-gates --commands, then--ran); run the spec, lint, formula and metadata-core unit tiers in full; open the draft PR (Fixes #22386); post the report. Then the seat dispatches the contract review (Clause-②: yes (widening)).
Generated by Claude Code
- Files (1)–(4) are mechanical consequences of adding any top-level
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22386,
"status": "done",
"branch": "claude/issue-22386-attached-on-read",
"pr": "#22425",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's session (this run is a subagent of it)",
"premise_still_valid": true,
"summary": "Patch round on the seat order 6074312931 (A, A, A). The five files from option A were added; nothing else widened, and stack.zod.ts is unchanged: metadata-core OBJECT_REFERENCE_POSITIONS gainsattachedOnRead: keep; the form ledger gains one ROOT_PATH omit row, with the ruling as its reason; the two composeStacks pins gain the key in their literal lists, plus one it.each row pinning that two stacks declaring different attachedOnRead are refused under objectConflict:'merge'; formula expression-refusal.ts types the optional unknown-fieldblock+leavesparams, with a third unknown-field pin case in expression-refusal-codes.test.ts (code, params and exact message). All four design choices are kept. The formula changeset is now minor; metadata-core is patch, because its dist ships the row. The liveness note and the AttachedOnReadSchema docblock name #22387's conformance test (keys and runtime value types) as the leaf types' reader. origin/main was merged once more (abd2545) through os-regen-merge.sh; the merge was clean. Draft PR #22425: body line 1 isFixes #22386, it carries the claim's Clause-② line verbatim, and #22387 follows. Deliberate deviation from the order's wording: the body does NOT put a closing keyword in front of #22211. The parser ignores context, so 'closes' followed by that card number would close #22211 when THIS PR merges. The body says instead that #22387 is the card that finishes #22211 and that this PR leaves #22211 open. The out-of-scope finding is in the Acceptance notes with #22387 as the carrier. The PR assignee is set to the card's (os-sales). No label written: the dispatch named none, and skip-changeset does not apply.",
"tests": "Final head 4ba8588 (dirty 0). Every build and test went through os-verify-lock, each VERDICT command-exit 0, shared-box seconds. Builds: closure buildpnpm --workspace-concurrency=2 --filter '@objectstack/lint...' --filter '@objectstack/metadata-core...' buildexit 0. Unit tiers in full: specvitest run --project local628/628 files, 18769 passed, 1 todo; lint 129/129 files, 5886 passed; formula 45/45 files, 1277 passed (the new pin case runs inside the unknown-fieldit, which asserts every case's code, params and exact message); metadata-core 17/17 files, 421 passed. Typecheck at 4ba8588, each with its test layer: spec 0, formula 0, lint 0, metadata-core 0. Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 110 commands; its stderr names the tree as objectstack-ai/objectstack at 4ba8588, with --repo checked against origin, and 21 paths against merge base abd2545. Every command ran with its exit code captured before any pipe. First pass: 107 exit 0, and 3 exit 3 PREREQUISITE NOT MET: check:skill-examples (client/client-react declarations), check:lean-entry-closure (objectql dist) and check:dual-build-cjs-loads (every package's dist). After the prerequisites were built under the lock (--filter '@objectstack/client-react...' --filter '@objectstack/objectql...' build, thenturbo run build --concurrency=2, exit 0), all three answered 0. dual-build-cjs-loads: 107 require entry points across 66 packages load.dispatch-gates --ranon that record: '110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. Among the 110, check:generated reports 15/15 artifacts current, and check:liveness, check-changeset-no-major, check-adr-0087-registration and check-empty-changeset are all 0. Ablation (unchanged since the last report; validate.ts blob at 4ba8588 = f7e24675, the ablated blob): run with ablation-replace.mjs, anchor x1 to x0 and blob f7e24675 to ac2f0590; 3 failed | 6 passed, the misspelt-leaf, no-near-leaf and previous-root pins red; restored to blob == HEAD with git diff HEAD empty; then 9/9. CI at report time: 22 check runs in_progress, 8 success, 2 skipped. Not waited on.",
"mcp_calls": "0",
"api_writes": "3 this round, all through the relay: pr_create (draft #22425, 8116 bytes sent and stored identical); POST issues/22425/assignees os-sales (via label-write.mjs, read back to match the target); POST issues/22386/comments (this report, via post-stamped.mjs). Plus the earlier round's report comment 6074289657. git push is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22387 · noted, not filed (in PR #22425's Acceptance notes) — the boundary of the ruling's literal 'field-existence set'. A declared block resolves at every surface the lint field index serves on that object (validation rules, hooks, field formulas, flow conditions), yet only rows the declaring service serves carry the block. No producer writes such a predicate today. Dedupe words: attachedOnRead served-row surfaces · read attachment validation rule formula · record.viewer write path predicate."
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsSeat review of PR #22425 at
4ba858882: ACCEPT. Next: the contract reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T05:54Z · holder of claim6073245703, as amended by6074312931. Thread-read: 6075183735 (the os-dev report after the order).The seat read the file list (21 files, +735 / -9) and the
ObjectSchemahunk.Accepted, read in the diff:
- The key:
ObjectSchema.attachedOnRead. It is a strict record of strict records. Block names and leaf keys both follow the field-name grammar.- A leaf's type is one of the four
Field.returnTypevalues (number | text | boolean | date), taken fromFieldTypeso the vocabulary cannot drift. - Its docblock says what it is not: no column, and no driver, form, list view, export, write path or translation bundle reads it. It also names its reader.
- The existing
superRefinerefuses a block that names no leaf, and a block named like a declared field.
- A leaf's type is one of the four
- The reader.
@objectstack/lint's field index admits each declared block name.@objectstack/formula's field-existence pass judges the second segment under the existingunknown-fieldrefusal, sorecord.viewer.can_acttis refused, naming the declared leaves.- The
blockandleavesparams are typed inexpression-refusal.ts. - The first-segment scan (
RECORD_REF_RE) is untouched, so an object without the key takes today's path. - Ablation: 3 pins red, then restored.
- The five files from the order, with nothing else widened and
stack.zod.tsunchanged:- the FLS
keeprow; - the form-ledger omit row;
- the two
composeStackspins, plus one row pinning the merge refusal; - the formula refusal params.
- the FLS
- Level:
@objectstack/specminor,@objectstack/formulaminor(additive public members),@objectstack/lintand@objectstack/metadata-corepatch, withClause-②: yes (widening).- The liveness row is present, and it names plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387's conformance test (keys and runtime value types) as the types' reader. - 110 / 110 derived gates ran.
- The spec, lint, formula and metadata-core tiers ran in full.
- The liveness row is present, and it names plugin-approvals:
- The dev's deviation is accepted, and it was the right call. The body puts no closing keyword in front of 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, because GitHub's parser would close that card when this PR merges. plugin-approvals:sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 is the card that finishes 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, and this PR leaves it open. The seat's order wording invited exactly that slip.
Out-of-scope finding: a declared block resolves on every surface the lint field index serves on that object → Acceptance notes. It reaches nothing today, and the carrier is #22387.
Next: the contract review at
CONTRACT_REVIEW_TIERon4ba858882→ every check green → landing (Fixes #22386). That unblocks #22387. IfLint & Repo Gatesreaches the step that #22422 tracks, the landing waits for #22422 and a merge ofmain.
Generated by Claude Code
- The key:
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsContract review FAIL on PR #22425 at
4ba858882(6075426209): one patch round for a sixth closed-world pin, then a fresh reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T06:13Z · holder of claim6073245703. Thread-read: the record6075426209on the PR. On this card, the newest comment is this seat's ACCEPT6075203768.The record judges the contract right: the key, its readers, the level and every flag. It fails on one red, and the red is this diff's.
- The red:
Test Core (6/6)readspackages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts:326("control:objectstill serves 44 top-level properties") asexpected 45 to be 44.CARD_PROPERTY_COUNTS.object: 44(:310–:313) is a closed-world pin that any new top-levelObjectSchemakey moves. Its docblock (:300–:309) records the same bump forimageFieldandprint.- The same shard is green on
main. - The seat's ACCEPT took the round-1 "metadata-protocol NOT MEASURED" without asking for the run. That miss is the seat's.
This round amends the claim
6073245703:- The sixth pin. In that test,
objectgoes from 44 to 45, and the docblock gets one sentence namingattachedOnRead. The file is test-only, so@objectstack/metadata-protocolneeds no changeset.domain:engine, declared on [PM seat] domain:engine — 🟢 os-project-manager #6367 in this act. - Record F2, inside the surface.
refuseAttachedBlockConflictsreads only the authored field map. A block named like an injected system column therefore parses clean and shadows that column's second segment in the validator.- Extend the refusal to the injected column names, but only if
object.zod.tscan reach them without a new export or an import cycle. - If it cannot, report the measurement and leave it.
- Extend the refusal to the injected column names, but only if
- Verify.
- Run
@objectstack/metadata-protocol's unit tier in full, along with spec, lint, formula and metadata-core. - Before the push, grep the tree for any other closed-world count or list of
ObjectSchema's top-level keys, and name what you find. ⛔ No seventh surprise. - Re-derive the gate list at the final head, run it, and run
--ran.
- Run
- Push, update nothing in the PR body except the measured lines (the seat edits the body), and post the report.
Record F1, the two other field-existence-set builders, gets no fix in this PR.
packages/mcp/src/mcp-http-tools.tsandservice-automation'ssetObjectSchemaResolverbuild their ownrecord.*field sets fromfields, so they do not read the new key.- They reach nothing until plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 declaresviewerand something writes arecord.viewer.*expression through those doors. - The carrier is plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387. The seat records it there in this act, so the conformance card names those two doors and either threads the key or proves them unreachable. .describe()and the changesets say "the expression validator". The round narrows that wording to the doors that read the key: the shared build validator.
Record F3 (
ObjectExtensionSchemacannot carry the key) → Acceptance notes. This is consistent with an in-code declaration beside the service.Then: a fresh contract review on the new head.
Generated by Claude Code
- The red:
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22386,
"status": "done",
"branch": "claude/issue-22386-attached-on-read",
"pr": "#22425",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's session (this run is a subagent of it)",
"premise_still_valid": true,
"summary": "Patch round on the seat order 6075451490 (contract review 6075426209 FAIL). New head a0545b8 is pushed; PR #22425 is still a draft and its body is not edited. (1) The sixth pin: in packages/metadata-protocol/src/protocol.meta-types-degenerate-derivation.test.ts, CARD_PROPERTY_COUNTS.object goes 44 to 45. The docblock gains one sentence beside the imageField and print precedents: 'object moved 44 → 45 the same way when it gained attachedOnRead'. Test-only, so no changeset. (2) F2, measured and left as the order allows. object.zod.ts cannot reach the injected column names without a new export or an import cycle. The per-object derivation resolveInjectedSystemColumns lives in data/injected-system-columns.ts, which imports isTenancyDisabled FROM ./object.zod, so importing it back is a cycle. Its column-name constants (PRIMARY_KEY_COLUMN, TENANT_SCOPE_COLUMN, OWNER_COLUMN, OWNING_BUSINESS_UNIT_COLUMN) are module-private consts at :79–:86. Only AUDIT_PROVENANCE_FIELDS (field-group-layout.ts) is reachable, and that is the audit family alone. refuseAttachedBlockConflicts is unchanged, no pin was added, and the AttachedOnReadSchema docblock records the measurement. (3) The wording is narrowed to the doors that read the key. The .describe(), the docblock, the ledger note and both changesets now say 'the shared build validator (@objectstack/lint's expression rule over @objectstack/formula, as os build / os validate run it)'. The spec changeset adds 'No other field-existence check reads it.' The docblock and ledger note name the two F1 doors (packages/mcp's expression tool and service-automation's flow-registration resolver) as carried by #22387. Reference docs were regenerated with gen:docs. Before this round's commits, origin/main was merged at 3054516 through os-regen-merge.sh (clean). At the head, git merge-base --is-ancestor origin/main HEAD exits 0. Closed-world inventory of ObjectSchema's top-level keys: a grep for files naming three or more of the rarely co-listed top-level keys, plus every count expression over served or shape properties. Each list or count it found, with its state: [a] metadata-core OBJECT_REFERENCE_POSITIONS (visited, round 2); [b] spec metadata-form-zod-reconciliation LEDGER (visited); [c] spec compose-stacks-merge-collection-refusal COLLECTION_KEYS_IN_SHAPE_ORDER (visited); [d] spec compose-stacks-collection-pipe-arm, two literal lists (visited); [e] metadata-protocol CARD_PROPERTY_COUNTS.object (visited this round, the only numeric count found; the same file's action:50 count is another type); [f] lint validate-expressions.test.ts #5017 READ_SURFACES obj expected (visited, round 1); [g] liveness/object.json and state-counts/object.md (visited and regenerated); [h] authorable-surface/data.json and the content/docs/references pages (regenerated). Not a pin to edit: authorable-surface.base.json, the anchor only gen:authorable-surface-base writes (its baseRev may lag; check:authorable-surface green). Derived, needing no edit: composeStacks' collection set (stack.zod.ts shape walk), the FLS test's declaredKeys, ObjectSchema.create()'s knownKeys, and the object form and its four generated translation bundles (form-derived; the key is an omit row). Named and read, not key lists: object.form.ts, lint validate-object-field-refs.ts / reference-integrity-suite.ts, metadata-protocol protocol-publish-drafts-object-field-refs.test.ts, spec object.test.ts, object-strictness-batch20.test.ts, kernel/metadata-authoring-lint.test.ts, compose-stacks-merge-config-object-refusal.test.ts (fixed-shape config objects only; attachedOnRead derives as a collection), liveness undrilled-containers.baseline.json (attachedOnRead has no child keys), sdui.manifest.json and component.zod.ts (component props). All of them pass in the full runs below. F3 (ObjectExtensionSchema cannot carry the key) is for the seat's Acceptance-notes edit.",
"tests": "Final head a0545b8 (dirty 0). Builds and tests ran under os-verify-lock, each VERDICT command-exit 0, shared-box seconds. Closure:pnpm --workspace-concurrency=2 --filter '@objectstack/metadata-protocol...' --filter '@objectstack/lint...' --filter '@objectstack/metadata-core...' buildexit 0. Unit tiers in full at a0545b8: @objectstack/metadata-protocolvitest run223 passed | 3 skipped of 226 files; 28325 passed | 19 skipped. The three skipped files are the env-gated live-mysql / live-postgres migration tests (seed-tenancy-backfill.live-mysql, .live-postgres, sys-setting-identity-index.live-mysql); protocol.meta-types-degenerate-derivation.test.ts passes, and it is the red Test Core (6/6) carried. @objectstack/metadata-core 17/17 files, 421 passed; @objectstack/formula 45/45, 1277 passed; @objectstack/lint 129/129, 5886 passed; @objectstack/spec--project local628/628 files, 18769 passed, 1 todo. Typecheck at a0545b8: metadata-protocol 0, spec 0 (incl. check:test-typecheck). formula, lint and metadata-core typecheck 0 at 4ba8588; their sources are unchanged since. Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat a0545b8 (stderr: the tree objectstack-ai/objectstack at a0545b8, --repo checked against origin, 22 committed paths vs merge base 3054516, working tree 0, untracked 0) derived 111 commands. That is the previous 110 pluspnpm check:durability-log-level, added by the metadata-protocol path. Before the run,pnpm exec turbo run build --concurrency=2exit 0 (73 tasks). All 111 ran witheval \"$cmd\" > log; status=$?captured before any pipe, and all 111 answered exit 0 on the first pass.dispatch-gates --ran: '111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. check:generated 0 (15/15 current), check:liveness 0, check:docs, check-changeset-no-major, check-adr-0087-registration and check-empty-changeset 0. PR-body lines for the seat to replace in '## Measured': head a0545b8; tiers metadata-protocol 223/226 files (3 env-gated live-DB skips), 28325 passed / 19 skipped; metadata-core 17/17, 421; formula 45/45, 1277; lint 129/129, 5886; spec 628/628, 18769 + 1 todo; gates 111 derived / 111 run / 0 NOT-MEASURED, all exit 0 on the first pass after a full turbo build. Ablation: still valid at a0545b8. packages/formula/src/validate.ts blob = f7e24675, the ablated blob; its evidence is in the PR body.",
"mcp_calls": "0",
"api_writes": "1 this round — POST issues/22386/comments (this report, via post-stamped.mjs through the relay). git push is not a REST write. The PR body is not edited, and no label or assignee write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #22387 · noted, not filed — F1, recorded by the seat on #22387 (6075461181). packages/mcp/src/mcp-http-tools.ts and service-automation's setObjectSchemaResolver build their own record.* field sets from fields only. This round narrows the wording to the build validator and names both doors in the docblock and the ledger note.",
"carrier: #22387 · noted, not filed — the boundary already in PR #22425's Acceptance notes: a declared block resolves at every surface the lint field index serves on that object. Dedupe words: attachedOnRead served-row surfaces · read attachment validation rule formula.",
"carrier: the seat's body edit · noted, not filed — F2: a block named like an injected system column is not refused at parse, because object.zod.ts cannot reach those names without an import cycle or a new export (measured above). Widening it would need resolveInjectedSystemColumns's column constants exported from a module object.zod.ts may import, or the derivation moved below object.zod.ts. Dedupe words: attachedOnRead injected column collision · refuseAttachedBlockConflicts injected · resolveInjectedSystemColumns cycle."
]
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22386,
"status": "done",
"branch": "claude/issue-22386-attached-on-read",
"pr": "#22425",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's session (this run is a subagent of it)",
"premise_still_valid": true,
"summary": "Merge round after the PASS 6076679507 (F5). New head e19c1e3 is pushed, and PR #22425 is still a draft; its body is not edited. A fresh worktree was taken from the remote head a0545b8, and origin/main 05c7c3f was merged with scripts/pm/os-regen-merge.sh (never rebased). packages/lint/src/validate-expressions.ts auto-merged. The single conflict was the #5017 PLUMBING hunk in packages/lint/src/validate-expressions.test.ts, resolved by keeping BOTH sides and dropping nothing. The list as merged, after 'templateRefusal': the #22394 block — 'declaredUserMembers', 'boundUserMembers', 'listedNames', 'tickedNames', 'membersRead', with main's comment — then the #22386 block, 'attachedOnReadIndex', with its comment. Six entries in all. The resolution was committed as merge 37cc130. The script's rerun then skipped step 1, took main's side of the generated paths only main moved, kept the branch's five generated files (main did not touch them), and left nothing more to commit. Verified in the merged file: the READ_SURFACES obj row still reads ['actions', 'attachedOnRead', 'fields', 'name', 'validations'], and validate-expressions.ts keeps all 13 attachedOnRead mentions with no markers. F4: the form-ledger omit row's why in packages/spec/src/system/metadata-form-zod-reconciliation.test.ts now ends 'and its one reader is the shared build validator' (commit e19c1e3, test-only string, no changeset). The merge did not touch packages/formula. It moved lint (#22394) and spec (hook.zod.ts plus tests). After the push, origin/main moved once more, to 081e6a0 (#22327, docs(spec)). At e19c1e3, git merge-base --is-ancestor origin/main HEAD exits 1; the control leg, 05c7c3f against the same HEAD, exits 0. That commit's six files (a changeset, two docs pages, three spec sources: contracts/security-service.ts, data/data-engine.zod.ts, kernel/execution-context.zod.ts) share no path with this PR's 22. They were not merged; the queue rebuilds onto main.",
"tests": "Head e19c1e3 (dirty 0). Under os-verify-lock (VERDICT command-exit 0, shared-box seconds):pnpm exec turbo run build --concurrency=2 --output-logs=errors-onlyexit 0. Unit tiers in full: @objectstack/lint 129/129 files, 5899 passed (5886 before the merge, plus #22394's 13); @objectstack/formula 45/45, 1277 passed (not touched by the merge, run anyway); @objectstack/spec--project local628/628 files, 18769 passed, 1 todo. lint typecheck exit 0. Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat e19c1e3 derived 111 commands, the same set as at a0545b8. Its stderr names the tree as objectstack-ai/objectstack at e19c1e3, with --repo checked against origin, and 22 committed paths vs merge base 05c7c3f, 764 changed lines (+753 / -11), under the 5000 threshold. All 111 ran with each exit code captured before any pipe; all 111 answered 0. check:generated, check:dual-build-cjs-loads and check:durability-log-level are among them.dispatch-gates --ran: '111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. PR-body lines for the seat's '## Measured' edit: 'Measured at e19c1e3 (origin/main 05c7c3f merged; the #5017 PLUMBING hunk keeps both sides)'; lint 129/129 files, 5899 passed; formula 45/45, 1277; spec 628/628, 18769 + 1 todo; metadata-protocol 223/226 files (3 env-gated live-DB skips), 28325 passed / 19 skipped, and metadata-core 17/17, 421 (both at a0545b8, untouched by this merge); gates 111 derived / 111 run / 0 NOT-MEASURED, all exit 0 after a full turbo build. The ablation evidence is unchanged: packages/formula/src/validate.ts blob f7e24675 at e19c1e3.",
"mcp_calls": "0",
"api_writes": "1 this round — POST issues/22386/comments (this report, via post-stamped.mjs through the relay). git push is not a REST write. The PR body is not edited, and no label or assignee write.",
"open_questions": [],
"out_of_scope_findings": []
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22386,
"status": "done",
"branch": "claude/issue-22386-attached-on-read",
"pr": "#22425",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the parent seat's session (this run is a subagent of it)",
"premise_still_valid": true,
"summary": "Merge round, with no source change by this PR. New head a5104d8 is pushed; PR #22425 is still a draft and its body is not edited. The worktree was fresh, at the remote head e19c1e3. origin/main was merged twice through scripts/pm/os-regen-merge.sh, never rebased, and neither merge conflicted. Merge 1, b5b7337, reached da159f7. It brought 081e6a0 (#22327), 806b03e (the #22435 revert of #22415's shard grading), bf492c8 (#22412), dd986d8 (#22439) and da159f7 (#22442). Its 37 incoming paths share none with the PR's 22. During the round, main gained #22315, the claim's named serial predecessor (#22110), in packages/lint/src/validate-expressions.ts and validate-expressions.test.ts. So merge 2, a5104d8, reached 2b61f2d. It brought 440bed6 (#22436), 4f4c4ed (#22396) and 2b61f2d (#22315). Both lint files auto-merged after a clean merge-tree dry run. Checked after merge 2: the READ_SURFACES obj row still reads ['actions', 'attachedOnRead', 'fields', 'name', 'validations']; validate-expressions.ts keeps its 13 attachedOnRead mentions; 'attachedOnReadIndex' stays in PLUMBING. Interdiff — the PR's delta is unchanged.git diff origin/main...HEADis 22 files, +753/-11, before and after both merges. Across merge 1 that patch is byte-identical (cmp). Across merge 2, its 764 changed lines match the pre-merge patch line for line (0 differences). With index lines and hunk-header line numbers stripped, the two patches are identical; only #22315's line offsets moved. Per path: 0 of the 22 changed on the branch side across merge 1, and 0 on main's side. After the push, main moved once more, to 3ca71b6 (#22440, metadata-protocol). git merge-base --is-ancestor origin/main HEAD exits 1; the control, 2b61f2d against the same HEAD, exits 0. Its 7 paths share none with the PR's 22, so it was not merged. Incident, not this diff's: the first full build at a5104d8 failed only in @objectstack/plugin-webhooks (TS2307: '@objectstack/types' not found). #22396 had added that dependency, and the worktree's node_modules predated the merge (AGENTS §9).pnpm install --frozen-lockfile, then a rebuild, fixed it: 73/73 tasks.",
"tests": "Head a5104d8 (dirty 0). Under os-verify-lock (VERDICT command-exit 0 on each acquisition, shared-box seconds).pnpm exec turbo run build --concurrency=2first exited 1 on plugin-webhooks (stale node_modules, above); spec, formula and lint were rebuilt in that run before it failed. Afterpnpm install --frozen-lockfilethe build exited 0, 73 successful of 73. Merge 2 moved lint, formula and spec, so all three tiers ran in full at a5104d8: @objectstack/lint 130/130 files, 5909 passed; @objectstack/formula 45/45, 1278 passed; @objectstack/spec--project local630/630 files, 18791 passed, 1 todo. Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat a5104d8 derived 111 commands, the same set as at e19c1e3 and b5b7337. Its stderr names the tree as objectstack-ai/objectstack at a5104d8, with --repo checked against origin, and 22 committed paths vs merge base 2b61f2d, 764 changed lines, under the 5000 threshold. All 111 ran with each exit code captured before any pipe, after the full build; all 111 answered 0.dispatch-gates --ran: '111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. (At b5b7337, before main moved again, the same 111 had also run all exit 0 and --ran answered 111/111/0/0; that head is superseded.) PR-body lines for the seat: 'Measured at a5104d8 (origin/main 2b61f2d merged; the PR delta unchanged, 22 files +753/-11)'; lint 130/130, 5909; formula 45/45, 1278; spec 630/630, 18791 + 1 todo; metadata-protocol 223/226 (3 env-gated live-DB skips) / 28325 passed and metadata-core 17/17 / 421, both at a0545b8, where their packages carried the PR's delta unchanged; gates 111 / 111 / 0 NOT-MEASURED, all exit 0. The ablation evidence holds: packages/formula/src/validate.ts blob f7e24675 at a5104d8.",
"mcp_calls": "0",
"api_writes": "1 this round — POST issues/22386/comments (this report, via post-stamped.mjs through the relay). git push is not a REST write. The PR body is not edited, and no label or assignee write.",
"open_questions": [],
"out_of_scope_findings": []
}objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22425 →
9af0005d5(Fixes #22386). The card is closedcompleted, and #22387's blocker is now closeddomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-09T11:26Z · holder of claim6073245703(amended by6074312931and6075451490).- Landed: PR feat(spec): ObjectSchema.attachedOnRead declares per-caller read attachments, and the shared validator judges record.BLOCK.LEAF against them #22425 merged through the merge queue at 2026-10-09T11:24Z as
9af0005d5. It has one parent,d87dff67c, and is an ancestor oforigin/main. There was no queue ejection.Fixesclosed this card. - Content check: all 22 PR files on
9af0005d5are blob-equal to the reviewed heada5104d854. That head carries the contract review PASS6079472486, after the patch round that answered the earlier FAIL6075426209. - What now holds (
@objectstack/specand@objectstack/formulaminor;@objectstack/lintand@objectstack/metadata-corepatch;Clause-②: yes (widening)):ObjectSchema.attachedOnReadis an optional key. It declares the blocks a service attaches to each row it serves, computed per caller and never stored. Each leaf names one of the four value typesnumber,text,booleananddate. A malformed declaration is refused at parse, at the offending key.- The shared build validator judges
record.BLOCK.LEAF(andprevious.) against the declared leaves, under the existingunknown-fieldcode. No new rule id or refusal code was added. - The key is not a field. No driver, form, write path or translation bundle reads it, and the field-level-security masker passes it through unchanged.
- Next: plugin-approvals:
sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 (domain:services) declaresvieweronsys_approval_requestand finishes 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. Its body lineBlocked-by: #22386now names a closed card, for that lane's unlock scan. It also carries record F1: the MCP expression tool's andservice-automation's own field sets. - 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 stays open until plugin-approvals:sys_approval_requestdeclares its per-callerviewerblock underattachedOnRead, with a conformance test againstattachViewers(#22211 ruling A, plugin-approvals half) #22387 lands. Its 8 shippedrecord.viewer.*predicates stay refused until the block is declared. - Acceptance notes carried from the reviews:
- F2: a block named like an injected system column is not refused at parse, because
object.zod.tscannot import those column names without a cycle. Carrier: none; the docblock records it. - F3:
ObjectExtensionSchemacannot carry the key. - Index access, a method call on a block and a third segment are deliberately unjudged.
- F2: a block named like an injected system column is not refused at parse, because
This act removes
pm:dispatchedfrom the closed card; the domain, area, priority and type labels stay.
Generated by Claude Code
- Landed: PR feat(spec): ObjectSchema.attachedOnRead declares per-caller read attachments, and the shared validator judges record.BLOCK.LEAF against them #22425 merged through the merge queue at 2026-10-09T11:24Z as
Filing gate: ③ a maintainer-directed task. This is the spec build card that the ruling
6070963704on #22211 names: "The spec seat files the two build cards the verdict names: spec (the new key, the second-segment judgement, the liveness-ledger row;Clause-②: yes (widening), aminorchangeset on@objectstack/spec) and 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. Part of #22211.The ruled shape (A, quoted from
6070963704)"
ObjectSchemagains one optional key, working nameattachedOnRead: a strict record of the blocks a service attaches to the rows it serves, computed per caller and never stored, each block naming its leaf keys and their types. It is not a field. Drivers, forms, list views, exports, write paths and translation bundles never read it."What this card builds
ObjectSchemagetsattachedOnRead: a strict record from block name to the block's leaf keys and their types. The final name is this card's to settle;attachedOnReadis the working name. The card also adds its liveness-ledger row.buildFieldIndex). The second segment is judged against that block's declared keys, under the existingunknown-fieldrule, sorecord.viewer.can_acttis refused. ⛔ No new rule, no new refusal code, and no exception forviewer(triage6054592515, and the ruling's "⛔ Not ruled").@objectstack/specisminor.@objectstack/lintand@objectstack/formulaarepatchif their files move.Clause-②: yes (widening). The contract review atCONTRACT_REVIEW_TIERis attached.Premises (each with a re-check, as the ruling read them at
b460153912)buildFieldIndexindexes everyfieldskey plusinjectedColumnsFor(obj). Re-check withgit show origin/main:packages/lint/src/validate-expressions.ts | sed -n 150,180p.RECORD_REF_REcaptures one segment afterrecord.. Re-check withgit show origin/main:packages/formula/src/validate.ts | sed -n 305,320p.attachedOnReadoccurs nowhere in the repository. Re-check withgit grep -n attachedOnRead origin/main.Order and serial notes
viewerunder the new key and is blocked by this one.packages/lint/src/validate-expressions.ts.visibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274 (domain:specseat 1) and PR feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 ([v18] flow text slots: read ADR-0032 §3's{{ }}delimiter instead of single-brace{token}(notify title/message and the other flow string slots), converting only what renders the same #22110) are in flight on the same file. lint: a select option'svisibleWhenreading a member the option check never binds (os.org.id,os.env,ctx.locale) passesos buildand the save door, and the server's option gate then faults open #22274 is on the field-rule pass, and feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 on the flow text slots.packages/formulaisdomain:engine, so the claimant declares it on [PM seat] domain:engine — 🟢 os-project-manager #6367.Generated by Claude Code