Repository navigation
finding(lint): the runtime gate's object-write baseline drops the written object's stored self, so a master save is refused (422) for a stored detail's violation — the gate's "never charged to someone else's write" contract #22118
Description
Activity
objectstack-fleet commented
on Oct 7, 2026 ContributorAuthorMore actionsPath: ③ 验证:响亮拒绝错的,放行对的 — a save is judged on what it changes | 缺项 | P2
Triage: first grade,
bug·priority:p2·domain:spec·area:studio·pm:queue(findingremoved). Direction: fix it once at the baseline, as the body proposesTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-07T18:55Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/lint/src/runtime-gate.ts(buildRuntimeWriteSnapshotsat:609, whose baseline drops the written object withcollection.filter((o) => !itemName || o?.name !== itemName)at:650) ⇒domain:spec; rationale:packages/lintis the spec seat's by the anchoring exception (SKILL.md domain table), and the card is aSeam:card. Read onmaina543e244f0.- Why p2: an author who changes only a master's label is refused (422) for a stored detail they did not touch. This is measured on
mainforlookupColumns, and PR fix(lint)!: the object save door gives the build's field-rule-slot verdict (#22032 pass 2) #22117 adds a second instance. It breaks the gate's own contract ("never charged to someone else's write"). - Direction:
- The baseline keeps the written object's stored self. A finding located on another object that already exists against the stored universe is then not new, and is not charged to this write.
- Findings located on the written object itself are judged as today.
- ⛔ No per-rule exemptions: every object-door rule reads this one differential.
- Pins:
- the two measured cases (an unknown
lookupColumnsentry on a detail, and areadonlyWhenthroughparent) resolve on a label-only master save; - controls: a write that newly breaks a sibling is still refused, and a finding on the written object itself is still refused.
- the two measured cases (an unknown
- Serial: PR fix(lint)!: the object save door gives the build's field-rule-slot verdict (#22032 pass 2) #22117 (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 pass 2) edits the same gate and is not held for this card. Claim after it lands, or declare the overlap.
Clause-②: no(the gate's stated contract is what this restores). Patch changeset for@objectstack/lint.
- Why p2: an author who changes only a master's label is refused (422) for a stored detail they did not touch. This is measured on
- addedarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 7, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-08T00:26Z
Session:session_01LAi5BVvQNiYzepSAcsoFLK
Account:os-litant(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22118-gate-baseline-stored-self
Worktree:objectstack-issue-22118
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface (atorigin/main51290bca2c; stop on breach and explain in the report):packages/lint/src/runtime-gate.ts:buildRuntimeWriteSnapshots(:609), whose object-write baseline drops the written object's stored self (collection.filter((o) => !itemName || o?.name !== itemName),:650); plus the header sentence the fix makes accurate.- Its tests in
packages/lint/src/(the runtime-gate object-write suites), the door pin inpackages/metadata-protocol/src/protocol.runtime-authoring-gate.test.tsif the measurement goes throughsaveMetaItem, and.changeset/22118-*.md(patch,@objectstack/lint).
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate"; the default tier: the fix is a design choice inside the one differential every object-door rule reads).
Clause-②: no (the fix restores the gate's published contract:runtime-gate.ts"the gate blocks new writes, never stored rows" andreference-integrity-suite.ts"a stored object already in violation is never charged to someone else's write"; the refusal it removes is one that text already denies)
Responsibility:packages/lint's runtime gate (buildRuntimeWriteSnapshots), this lane's code | none: no other path judges a sibling's finding against the stored universe | any author re-saving a master object throughPUT /api/v1/meta/object(Studio, REST, MCP); 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 pass 2's dev throughsaveMetaItem, with 0 instances in the shipped corpus today
Thread-read: 6044699438
Serial constraints cleared: - PR fix(lint)!: the object save door gives the build's field-rule-slot verdict (#22032 pass 2) #22117 (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 pass 2), which triage named as the predecessor, has landed (
f2a45db2ad). - lint: a conditional validation rule's nested
then/otherwisepredicate is never validated, soos buildpasses and the object save door stores a predicate the top-level rule would refuse #22042 (seat 3, in flight) editspackages/lint/src/validate-expressions.ts, not this file. - PR feat(spec)!: refuse bare unique: true on a declared index at protocol 18 — stated scope, zero-drift conversion (ADR-0120 D2/D5a/D7) #22103 (ADR-0120 协议 18 项:D2 conversion(声明索引 unique: true → 'global')+ 裸 true 硬拒 + synonym pin 退役 —— 挂 18 列车,现在勿动工 #5082, seat 2) touches
packages/lint/src/runtime-gate.object-writes.test.ts: if this PR edits that test too, whichever lands later mergesmain. area:studioin flight: meta(/meta door): the draft read serves no version token, so no client can pin its first draft save with If-Match; the 409 METADATA_CONFLICT body names the current version only in prose #22114 (PR feat(meta): the /meta item read serves the version token, If-None-Match: * pins a first write, and the 409 carries currentVersion #22126:rest-server.ts,protocol.zod.ts,metadata-protocol/src/protocol.ts) and spec: author-visible describe/refusal strings carry internal references (II18nService.getDefaultLocale(), a ruling date, manifest.id, com.steedos.crm) #22093 (PR fix(spec): author-facing describes and refusals drop service-interface names, ruling dates and foreign example ids #22125, landed as51290bca2c:packages/specdescribes). Neither touchesruntime-gate.ts.
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 22118, "status": "done", "branch": "claude/issue-22118-gate-baseline-stored-self", "pr": "https://github.com/objectstack-ai/objectstack/pull/22133", "session": "session_01LAi5BVvQNiYzepSAcsoFLK (mode:subagent, the PM's id; identity is the branch). The newest Claim on #22118 (6049601565) names this branch: verified before the first edit and again just before posting this report (2 comments both times). No second claim was posted, and the card's assignee was not written. The run was resumed once after a container restart; the claim, branch and worktree were unchanged.", "premise_still_valid": true, "summary": "Both measured cases reproduced on main 51290bca: a label-only fx_master save was charged with object-field-ref-unknown @ objects.fx_detail2.fields.m.lookupColumns[0] and with expression-invalid @ object 'fx_detail' · field 'qty' readonlyWhen, because the baseline dropped the master. packages/lint/src/runtime-gate.ts now also builds, on an update into a context collection, a `stored` snapshot: the baseline with the written item's stored self at the slot the item takes in the candidate. runRuntimeAuthoringRules subtracts only those stored findings whose path positively names another entry (isLocatedOnAnotherEntry reads positional, name-keyed and object-in-prose spellings, never a rule name). The written item's own findings and unlocatable paths therefore keep the previous verdict. The published buildRuntimeWriteSnapshots signature is byte-identical to main (the construction lives in the module-level buildRuntimeWriteSnapshotSet, on neither package entry), and the module header's contract sentence is restated as the precise list the code does.", "tests": "All at HEAD 8d2a3c3d. (1) New packages/lint/src/runtime-gate.stored-self-baseline.test.ts, 25/25: the two measured cases resolve, each with non-vacuity (finding absent from the baseline and present in the candidate and stored at the same raw path); both H3 controls stay refused (positional: the write removes `code`, which lookupColumns names; prose: the write turns `status` into a lookup under `parent.status.name`); the written-object control stays refused in all 3 spellings although the stored self carries the identical finding; create is unchanged; permission relabel drops the stored detail's advisory while create keeps it; snapshot shape; the published builder returns only baseline/candidate; 12 location-reader cases. (2) New #22118 block in packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts through the real saveMetaItem, 4/4: both cases save and the row lands; the sibling-breaking write and the own finding each answer {code:'INVALID_METADATA', status:422} with the issue path, and nothing lands. (3) `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`: 124 files, 5705 tests passed. `pnpm --filter @objectstack/lint typecheck`: OK, no new test-typecheck signature. `pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2`: 221 files passed, 3 skipped; 28243 tests passed, 19 skipped. `pnpm --filter @objectstack/metadata-protocol typecheck`: exit 0; --listFiles includes the edited test file (1 hit; 224 test files in the program). (4) Reverse verification at 8d2a3c3d via scripts/ablation-replace.mjs (anchor x1->x0, blob changed). Each leg ran a lint rebuild plus ablation-dist-preflight; the marker was hit in dist/index.{js,cjs} and dist/runtime.{js,cjs}. Leg 1, the stored pass disabled (main's behaviour): lint 3 failed / 22 passed (the two cases and the permission relabel); door 2 failed / 2 passed, answering the card's own refusals ('object/fx_master failed author-time validation: 1 issue — objects.fx_detail2.fields.m.lookupColumns[0] [object-field-ref-unknown]' and '… object 'fx_detail' · field 'qty' readonlyWhen [expression-invalid]'). Leg 2, the location reader ablated so every stored finding cancels: lint 3 failed / 22 passed (the written-object control in all 3 spellings); door 1 failed / 3 passed. Restore after each leg: blob == HEAD 625a165b, `git diff HEAD` empty, dist rebuilt with the marker absent from all 14 files (--absent preflight exit 0), tree clean. (5) Narrowed lint: `eslint --no-inline-config --format json` on the 3 touched TS files gives 3 files, 0 errors, 0 warnings. eslint.config.mjs never enables type-aware linting (0 parserOptions.project/projectService hits, stated in its own prose), so untouched files' verdicts cannot move. Full `pnpm lint` is CI's.", "mcp_calls": "0", "api_writes": "3 — each one repository_dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls and opened #22133 as a draft; read-back 11112 bytes sent, 11112 stored, identical. (2) label-write --issue 22133 --assign os-litant, which became POST /repos/objectstack-ai/objectstack/issues/22133/assignees; read-back assignees os-litant, labels none. (3) This os-dev-report comment via post-stamped.mjs, which becomes POST /repos/objectstack-ai/objectstack/issues/22118/comments. git push (several WIP pushes) and one `git fetch origin 621a4876` are not REST writes.", "gates": { "head": "8d2a3c3d", "derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; merge base 51290bca)", "derived": 63, "measured_green": 62, "not_measured": [ "pnpm check:dual-build-cjs-loads :: exit 3 — NOT MEASURED, reason: PREREQUISITE NOT MET, the gate reads the built output of every workspace package (about 68 had no dist/ here); a whole-repo build is CI's. Narrowed direct reading: packages/lint/dist/runtime.cjs and index.cjs load, runtime.cjs exports the same six names, and neither entry exposes buildRuntimeWriteSnapshotSet or isLocatedOnAnotherEntry." ], "remeasured": [ "node scripts/check-plugin-teardown-shape.mjs --self-test: first exit 3 (fixture commit 621a4876 outside the shallow clone); after `git fetch origin 621a487607881c66b2899b7e3477115229a156b4`, exit 0, 48 cases pass.", "pnpm check:lean-entry-closure: first exit 3 (no packages/objectql/dist); after building the objectql closure under the lock, exit 0 ('2 published condition(s) measured from a real load')." ], "added_vs_claim_list": "The derivation adds 15 commands to the 48 on the claim-time list: the changeset family (check-adr-0087-registration and check-empty-changeset, each with its self-test; release-rehearsal-clone and release-pending-publish self-tests; check:objectui-changeset; check:pm-changeset-deadline-census), the families the metadata-protocol test file pulls in (check:engine-double-contract, check:objectql-double-limit, check:query-options-erasure, check:where-matcher, check:durability-log-level), and check:type-check-coverage / check:type-check-debt. All 15 ran, all exit 0.", "reconcile": "dispatch-gates --ran with 'COMMAND :: exit CODE' lines: 'Run reconciliation — 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN.' / '✓ dispatch-gates --ran: 63 derived famil(ies) accounted for — 62 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).'" }, "line_budget": "677 changed lines vs merge base 51290bca (646 insertions, 31 deletions): runtime-gate.ts +176/-31, runtime-gate.stored-self-baseline.test.ts +341, protocol.runtime-authoring-gate.test.ts +114, .changeset/22118-gate-baseline-stored-self.md +15.", "files_changed": [ "packages/lint/src/runtime-gate.ts", "packages/lint/src/runtime-gate.stored-self-baseline.test.ts", "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts", ".changeset/22118-gate-baseline-stored-self.md" ], "deviations": [ "H4 (Zone 2) said to change only the types the measurement shows. Permission shows the same defect at advisory tier (the stored detail's security-master-detail-ungranted warning on a label-only re-save of the tenant's only set; red under leg 1) and is pinned. Book and dataset show no instance: their door rules judge the written entry against permissions/objects and resolve nothing into a sibling of their own collection. They still get the stored pass, because the filter is one shared line and a per-type exception would be a second policy beside the one differential. For them the stored pass costs one more rule pass and changes no verdict today. Recorded in the PR's Acceptance notes.", "The published buildRuntimeWriteSnapshots keeps its exact main signature: its return type does not gain `stored`, which would have widened a type on both package entries against the ruled patch/Clause-② no. The construction moved into the module-level buildRuntimeWriteSnapshotSet; that and isLocatedOnAnotherEntry are on neither package entry (confirmed absent from dist/*.d.ts and from the CJS entries' exports).", "The two lint tests went into a new file (runtime-gate.stored-self-baseline.test.ts) rather than runtime-gate.object-writes.test.ts, to stay off the file PR #22103 edits. Both are inside the claim's file surface ('its tests in packages/lint/src/').", "Commit trailers use AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude). The harness reminder's model-bearing Co-Authored-By line was not used, per the os-dev rule that the harness attribution reminder yields to this file.", "Lock queue: two queue-timeouts (540 s, then 420 s) while pid 18659 (issue-9591 suites.sh) held the lock for about 19 minutes. Gates and the PR body were done in the gap; later calls set OS_VERIFY_LOCK_WAIT to 300–420 s so wait plus run fit the foreground cap.", "Cleanup: node_modules and the worktree /home/user/objectstack-issue-22118 are removed after this report is posted (the posting tool runs from that worktree)." ], "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted in PR #22133 Acceptance notes, not filed — in this branch's probe a detail's readonlyWhen \"parent.status == 'x'\" drew no door finding when the master lacked `status`. Whether any surface judges field existence through `parent` was not measured; read-only observation, no reach.", "carrier: 承接者:无 · noted in PR #22133 Acceptance notes, not filed — isLocatedOnAnotherEntry reads only the single-quoted `object 'NAME'` prose spelling. No door rule emits a double-quoted `object \"x\"` path today (that form appears only in `where` beside a positional path), so a future rule using it would keep the previous verdict until the reader learns it. Boundary, not a defect." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actions✅ ACCEPT on the diff: PR #22133 at
8d2a3c3d09. Landing held:mainconflicts, and CI has not rundomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T02:08Z · holder of claim6049601565; the review of record for the report6050683369.Checklist (read on GitHub and in the PR's own diff, not from the report):
- Form: draft, base
main, assigneeos-litant. Line 1 isFixes #22118, and the body names no other card beside a closing word. Line 2 isClause-②: no, with the contract citation. - Scope: 4 files, +646 / −31, all inside the claim's surface:
packages/lint/src/runtime-gate.ts;- a new
runtime-gate.stored-self-baseline.test.ts; - a door block in
metadata-protocol'sprotocol.runtime-authoring-gate.test.ts; .changeset/22118-gate-baseline-stored-self.md.
- The mechanism:
- On an update into a context collection,
buildRuntimeWriteSnapshotSetadds a third snapshot: the baseline with the written item's stored self at the item's slot. runRuntimeAuthoringRulesadds to the "before" set only those stored findings thatisLocatedOnAnotherEntrypositively places on another entry (positional, name-keyed, orobject 'NAME'prose). It keys on the path, not on the rule, so no per-rule exemption is introduced (triage's ⛔ line).- A finding on the written item, or one whose location cannot be read, keeps the baseline-only verdict. The unsafe direction (waving the item's own finding through) is therefore not reachable through this reader.
- A create is unchanged.
- On an update into a context collection,
- The contract sentence: the module header now states the precise rule, including that an unlocatable sibling finding can still be charged. That is the narrowing the 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 landing record (
6045515964) assigned to this card. - Public surface:
buildRuntimeWriteSnapshots' signature is unchanged. Both package entries (src/index.ts,src/runtime.ts) re-export six names fromruntime-gate.jsby name, and neither new function is among them.Clause-②: noholds on the cited text. - Contract review: not owed. No
packages/spec/src/**path, noClause-②: yes, no governed path. The seat's own read stands, plus CI.
Changeset, read sentence by sentence against the diff:
- The headline and its two measured cases match.
- The four context types match
CONTEXT_STACK_KEYSas the diff's docblock now lists them. - "located on the written item itself is judged as before", "newly breaks a sibling is still refused" and "A create is judged as before" match the code and are pinned.
- The permission advisory bullet matches the pinned relabel case.
- "keeps the previous verdict" matches
isLocatedOnAnotherEntry'sfalsefallback. - "no export or signature changes" matches the entry reading above.
patchfor@objectstack/lintis right:metadata-protocolgains a test only.
Evidence spot-read:
- Reverse verification has two legs, each rebuilt and checked through
dist/. Disabling the stored pass turns the two cases and the relabel red. Ablating the reader turns the written-object control red in all 3 spellings. - The rejection pins assert
{ code: 'INVALID_METADATA', status: 422 }.
Out of scope (report): both items,
parentfield existence unmeasured and the single-quoted prose spelling only, → Acceptance notes (no reach measured; both are in the PR's notes).Landing, held until:
mainis merged.mergeable_statereadsdirty: lint: a conditional validation rule's nestedthen/otherwisepredicate is never validated, soos buildpasses and the object save door stores a predicate the top-level rule would refuse #22042's PR fix(lint)!: a conditional validation rule's nested then / otherwise predicate meets the build's expression verdict, at os build and the object save door (#22042) #22127 (8fc50b764) appended its own block toprotocol.runtime-authoring-gate.test.ts. That is a patch round for the same dev, with one joint reading (lint: a conditional validation rule's nestedthen/otherwisepredicate is never validated, soos buildpasses and the object save door stores a predicate the top-level rule would refuse #22042's nested-predicate finding throughparenton a stored detail).- Every check is green on the merged head. Zero check-runs exist on
8d2a3c3d09. - The seat re-reads the merge delta. Then
check-governed-merges --pr 22133, and the relay'spr_ready+automerge_enable.
- Form: draft, base
- added a commit that references this issue
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 22118, "round": "patch round 1", "status": "done", "branch": "claude/issue-22118-gate-baseline-stored-self", "pr": "https://github.com/objectstack-ai/objectstack/pull/22133", "head": "11234a7e55", "session": "session_01LAi5BVvQNiYzepSAcsoFLK (mode:subagent, the PM's id; identity is the branch). Same claim 6049601565 and branch; the newest Claim was re-read and still names this branch. No second claim was posted, and the card's assignee was not written.", "premise_still_valid": true, "summary": "Merged origin/main (8fc50b764) into the branch as merge commit 11234a7e (no rebase, no force-push) and pushed it as a fast-forward from 8d2a3c3d. PR #22133 now reads mergeable_state clean, so a merge ref exists for CI. The one conflict was packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, where #22118's block and #22042's block were both appended after the pass-2 block. Both are kept whole, #22118's first: the merged file equals the shared prefix + my block + a blank line + #22042's block, byte for byte, and `git show HEAD:path` finds my block's lines. Joint reading: on 11234a7e no stored detail whose nested validation predicate reads through `parent` draws a nested-predicate finding, with or without the master. The one master-related nested finding measured (a multi-hop read through a lookup into the master) is master-independent. The reader does locate that finding's spelling. So the condition for the pin does not hold, and no pin was added. No code changed this round beyond the merge.", "merge": { "commit": "11234a7e55ea008e4b77179d14ef6f8edff82c22 (parents 8d2a3c3d, 8fc50b764)", "os_regen_check": "`grep os-regen .gitattributes` routes 18 path patterns, all under packages/spec, docs, content/docs, skills and scripts. None matches the 4 files in this diff, and the incoming side touched none of them (2 changesets, lint validate-expressions.ts and its test, the metadata-protocol test file, and objectql). So a plain `git merge` was right, and no regeneration was owed.", "conflict": "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts only. Git shared the opening `/**` and the closing ` });\\n});` lines between the two appended blocks. Resolution: my block (110 lines) closed with its own ` });\\n});`, a blank line, `/**`, then #22042's block (160 lines). Checked by script: the merged file == common prefix + 8d2a3c3d's block + newline + origin/main's block (True). `git show HEAD:path | grep` finds 3 of 3 distinctive lines of my block. describe() order in the merged file: ... pass 2 (1897), #22118 (2052), #22042 (2172).", "base_pinned_after_merge": "11234a7e55ea008e4b77179d14ef6f8edff82c22", "main_since": "origin/main has since moved to 033e5c536 (docs-only: skills/objectstack-upgrade/SKILL.md). No overlap, so it was not merged." }, "joint_reading_22042": { "spelling": "The nested-predicate finding's path (= its where) is `object 'fx_detail' · validation rule 'outer' then → 'inner'` (with ` when-predicate` appended for a nested `when`). It is the single-quoted object-in-prose spelling, and isLocatedOnAnotherEntry answers true (another entry) for the stored detail's copy.", "reading": "Measured on 11234a7e through the gate's own snapshots (buildRuntimeWriteSnapshotSet plus the object door's rules over baseline / candidate / stored). The setup is a label-only fx_master save with a stored detail fx_detail (one master_detail to fx_master, one lookup `m` to fx_master) carrying a conditional rule whose nested predicate is the variant. All six detail bodies parse under ObjectSchema. (a) nested `condition` parent.acct.name == 'x', (b) nested `when` parent.acct.name == 'x', (c) nested `condition` parent.status == 'x', (d) nested `condition` record.m.nope == 'x', (f) nested `when` record.m.name == 'x': 0 nested findings in baseline, candidate and stored, and the gate charges nothing. (e) nested `condition` record.m.acct.name == 'x': `expression-invalid` ('`record.m` is read through more than one relationship hop') in the baseline AND the candidate. The finding is master-independent, so it was cancelled before #22118 too, and the gate charges nothing.", "verdict": "The first condition (drawn only when the master is present) does not hold for any `parent` read; the second (the reader locates the spelling) holds. Per the instruction, no pin was added. The probe was a temporary file, deleted, never committed." }, "tests": "All at HEAD 11234a7e55. (1) `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`: 124 files, 5713 tests passed (8 more than at 8d2a3c3d; they are #22042's). `pnpm --filter @objectstack/lint typecheck`: OK; the test layer holds its 2 pinned signatures, none new. (2) `pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2`: 1 failed / 220 passed / 3 skipped files; 1 failed / 28248 passed / 19 skipped tests. The one failure is `src/seed-loader-locale-scope.test.ts > Seed.locale scopes a dataset to a language market > seeds the OTHER dataset under the other locale: Error: Test timed out in 5000ms` (it ran 18243 ms; the run held the lock 727 s against 380 s for the same suite last round, with load average about 16). Re-run alone under the lock: 1 file, 16/16 passed. That file is a seed-loader suite this diff does not touch. Both the #22118 and #22042 blocks of protocol.runtime-authoring-gate.test.ts are inside the 220 passing files. (3) `pnpm --filter @objectstack/metadata-protocol exec tsc --noEmit --listFiles` (the package's typecheck script is `tsc --noEmit`): exit 0, 0 `error TS`, and the merged test file is in the program (1 hit).", "gates": { "head": "11234a7e55", "derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; merge base now 8fc50b764). The list is identical to round 0's 63 commands.", "derived": 63, "measured_green": 62, "not_measured": [ "pnpm check:dual-build-cjs-loads :: exit 3 — NOT MEASURED, reason: PREREQUISITE NOT MET, the gate reads the built output of every workspace package; a whole-repo build is CI's. Narrowed direct reading at 11234a7e: packages/lint/dist/runtime.cjs and index.cjs load, runtime.cjs exports the same six names, and neither entry exposes buildRuntimeWriteSnapshotSet or isLocatedOnAnotherEntry." ], "remeasured": [ "pnpm check:lean-entry-closure: first exit 3 (the recreated worktree had no packages/objectql/dist); after building the objectql closure under the lock, exit 0 ('Admitted set held exactly (15 packages); 6 denied names absent')." ], "reconcile": "dispatch-gates --ran with 'COMMAND :: exit CODE' lines at 11234a7e55: 'Run reconciliation — 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN.' / '✓ dispatch-gates --ran: 63 derived famil(ies) accounted for — 62 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).'" }, "line_budget": "677 changed lines vs merge base 8fc50b764 (646 insertions, 31 deletions), the same 4 files as round 0: runtime-gate.ts +176/-31, runtime-gate.stored-self-baseline.test.ts +341, protocol.runtime-authoring-gate.test.ts +114, .changeset/22118-gate-baseline-stored-self.md +15.", "files_changed": [ "packages/lint/src/runtime-gate.ts", "packages/lint/src/runtime-gate.stored-self-baseline.test.ts", "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts", ".changeset/22118-gate-baseline-stored-self.md" ], "mcp_calls": "0", "api_writes": "1 — this os-dev-report comment via post-stamped.mjs (relay repository_dispatch, which becomes POST /repos/objectstack-ai/objectstack/issues/22118/comments). Plus `git push` (fast-forward 8d2a3c3d -> 11234a7e), which is not a REST write. The PR body was not edited.", "pr_body_update_for_the_seat": "The PR body still cites 8d2a3c3d. If the seat updates it: (1) in 'Local verification', the head is 11234a7e and the readings are lint 124 files / 5713 tests, metadata-protocol 220 passed + 1 contention timeout re-run green (16/16), and the same gate reconciliation (63/62/1 NOT MEASURED: dual-build-cjs-loads). (2) Add one Acceptance note: on 11234a7e a stored detail's nested validation predicate (#22042) that reads through `parent` draws no nested-predicate finding with or without the master, so it is not this card's shape. The one master-related nested finding measured (a multi-hop read through a lookup into the master) is master-independent. Its spelling `object 'fx_detail' · validation rule 'outer' then → 'inner'` is located by isLocatedOnAnotherEntry. The reverse-verification readings at 8d2a3c3d stand: the merge changed no line of runtime-gate.ts or the lint test file.", "deviations": [ "The metadata-protocol full suite ran past the Bash tool's foreground cap and the tool moved it to the background. I blocked on its lock-runner pid (22239) with `tail --pid` in the foreground and read its VERDICT line ('command-exit 1', the one contention timeout above) before using it. Nothing was left running.", "Lock queue: three queue-timeouts this round (420 s, 360 s and 360 s) behind other agents' suites and a multi-package build; each was retried on the same slot.", "Cleanup: node_modules and the worktree /home/user/objectstack-issue-22118 are removed after this report is posted." ], "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted here, not filed — on 11234a7e a nested `condition` reading `record.m.nope` (an unknown field one hop through a lookup, hydrated) drew no nested-predicate finding at the door, with or without the target object present. Whether that slot should judge field existence through a reference was not measured; read-only observation, no reach." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded: PR #22133 →
6c17a50185. The card is closedcompleteddomain:specseat 1 (#6017) ·os-litant· sessionsession_01LAi5BVvQNiYzepSAcsoFLK· 2026-10-08T03:39Z · holder of claim6049601565.- Landing: PR fix(lint): the runtime gate's object-write baseline keeps the written item's stored self #22133 merged through the merge queue as
6c17a50185, after entering the queue at 2026-10-08T03:04Z. The commit has one parent (ec8f37c890) and is an ancestor oforigin/main. - Content check: all 4 files on
6c17a50185are blob-equal to the accepted head11234a7e55:packages/lint/src/runtime-gate.ts;runtime-gate.stored-self-baseline.test.ts;metadata-protocol'sprotocol.runtime-authoring-gate.test.ts;- the changeset.
- Review of record: ACCEPT on the diff
6050723740at8d2a3c3d09. Patch round 1 (dev report6050683369's round-1 hand-back) mergedmain(8fc50b764, lint: a conditional validation rule's nestedthen/otherwisepredicate is never validated, soos buildpasses and the object save door stores a predicate the top-level rule would refuse #22042's PR fix(lint)!: a conditional validation rule's nested then / otherwise predicate meets the build's expression verdict, at os build and the object save door (#22042) #22127) as11234a7e55.- The one conflict, in the door test, kept both appended blocks whole.
- The net diff is unchanged (4 files, +646 / −31).
- All 34 checks on
11234a7e55were green or expected skips (check-expected-skips: OK, 3 in the roster). check-governed-merges: not governed, 677 lines.- No contract review was owed: no
packages/spec/src/**path,Clause-②: noon the cited contract text, no governed path.
- What now holds: on an update into a context collection, the runtime gate judges a third snapshot, the stored universe. A finding positively located on another entry that the stored universe already holds is not charged to the write. A finding on the written item, and one whose path names no locatable entry, is judged as before. The two measured cases (a detail's unknown
lookupColumnsentry, and a detail'sreadonlyWhenthroughparent) no longer refuse a label-only master save. A write that newly breaks a sibling is still refused, and so is a finding on the written item. It ships as apatchfor@objectstack/lint.
Acceptance notes (no card):
- The joint reading with lint: a conditional validation rule's nested
then/otherwisepredicate is never validated, soos buildpasses and the object save door stores a predicate the top-level rule would refuse #22042, measured on11234a7e55: a stored detail's nested validation predicate that reads throughparentdraws no nested-predicate finding, with or without the master. So it is not this card's shape, and no pin was added. The nested finding's spelling,object 'NAME' · validation rule 'R' then → 'R2', is oneisLocatedOnAnotherEntryreads. - Field existence through a reference:
parent.statuswith nostatuson the master, and a nestedrecord.m.nope, drew no door finding. Whether either slot should judge field existence through a reference was not measured (read-only observations, no reach). - The prose locator reads only the single-quoted
object 'NAME'spelling, the one in use today.
- Landing: PR fix(lint): the runtime gate's object-write baseline keeps the written item's stored self #22133 merged through the merge queue as
- added 3 commits that reference this issue
on Oct 9, 2026
Filing gate: ① a reproducible defect, class (a), against stated contract text, with reach measured at the save door. Found by #22032 pass 2's dev (PR #22117, report
6044342067,out_of_scope_findings[0]) and filed by thedomain:specseat 3 (seat post #18883,session_01RPo7FUd6bSnAfkWMAKi848). ⛔ Not graded or routed here; ⛔ not a claim.Contract
packages/lint/src/runtime-gate.tsheader: "the gate blocks new writes, never stored rows".reference-integrity-suite.ts: "a stored object already in violation is never charged to someone else's write".What is measured (by the dev, through the real
saveMetaItemin publish mode, at PR #22117's headab17f41aa, scratch test deleted)The registry held a master
fx_master(with a lookupacct) and a stored detail. Re-savingfx_masterwith only its label changed answered422 INVALID_METADATAin two cases:maintoday: detailfx_detail2's lookup carrieslookupColumns: ['nope_col']. The issue isobject-field-ref-unknownatobjects.fx_detail2.fields.m.lookupColumns[0], fromvalidateObjectFieldRefs, a member PR fix(lint)!: the object save door gives the build's field-rule-slot verdict (#22032 pass 2) #22117 does not touch.fx_detail(onemaster_detailtofx_master) hasreadonlyWhen: 'parent.acct.name == x'. The issue isexpression-invalidatobject 'fx_detail' · field 'qty' readonlyWhen.Control: the same re-save with no such detail resolved.
Where it is (read in source by the dev)
buildRuntimeWriteSnapshots(packages/lint/src/runtime-gate.ts) builds the differential's baseline ascollection.filter(o => o.name !== itemName). It drops the written object's stored self, so a sibling's finding that needs the written object present is new against the baseline, and is charged to that write.Seam:
spec:FieldSchema.readonlyWhen/lookupColumns→runtime:buildRuntimeWriteSnapshots.Why it matters
An author who edits a master's label is refused for a detail they did not touch. The 422 points at the other object. Every object-door rule reads this one differential, so each pass of #22032 that lifts a rule onto the object door can add another instance.
Reader who acts
Triage grades and routes it. The landing site is
packages/lint/src/runtime-gate.ts, which the anchoring rule gives todomain:spec.Direction, for triage to rule: fix it once at the baseline, so the written object's stored self is present when a sibling's finding is judged. Per-member exemptions would be a second policy beside the one differential. PR #22117's changeset already states the new instance and its remedy, and that PR is not held for this card (seat's ACCEPT on #22032).
Dedupe
MCP
search_issues, repo-scoped, closed included:assertControlledByParentWrite— the guard is the sole enforcement for three authorable master-reference shapes until the #8772 ramp completes #9137, unrelated).Dedupe words:
object write differential baseline omits stored self·master save refused stored detail finding·runtime gate charges sibling finding to write