Skip to content

fix(lint)!: the object save door gives the build's validation-rule verdict (#22032 pass 1) - #22041

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-22032-object-door-validation-predicates
Oct 7, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-22032-object-door-validation-predicates

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22032
Clause-②: no (narrowing)

This is pass 1 of #22032: the validation-rule predicates. Passes 2 to 4 stay fenced, and the card stays open for them: the field-rule slots, option visibleWhen, and the object's action predicates.

What changes

The object save door gives the build's verdict on a validation rule's predicates. formulas.mdx says "the same validateExpression validator backs os build and metadata registration". PR #22031 (#22019) made that true for formula fields and fenced every other object-borne pass off the door by name. So an object whose validations[].condition was sqrt(record.amount) > 1, or a bare amount > 1, still saved with a 200, while os build refused both at error.

  • The change is in the fence, not the registry. runStackExpressionPasses (packages/lint/src/validate-expressions.ts) no longer empties the validation-rule loop on an object write. On that write the rule now runs two passes, each at the build's own position in the walk:
  • No registry change. The validateStackExpressions entry already declared object after PR fix(lint,objectql)!: the object save door gives the build's formula verdict, and a formula fault is logged once per object and field #22031, so runtimeAuthoringRulesFor('object') already dispatched it. runtime-gate.ts is untouched. In authoring-rules.ts only comments move: the entry's comment and the AuthoringRuleContext.runtimeWriteType docblock. The latter is the one line that reaches a built .d.ts.
  • The fence flag is renamed from fieldFormulasOnly to objectWrite. The old name would have been false. Its docblock (StackExpressionOptions.runtimeWriteType) now names the two admitted passes and the three fenced ones.
  • The door's verdict is the build's finding. The door's 422 issue and runAuthoringRules('build', …) give the same rule (expression-invalid), location (object 'fx_rule' · validation 'amount_rule'), message and hint. The pins compare these key by key.
  • No code change in packages/metadata-protocol. Only its test file gains the door-level pins.

Pins

  • Lint door: packages/lint/src/runtime-gate.object-validation-writes.test.ts (new, 8 tests). It covers:
    • LIT refusals: sqrt(record.amount) > 1, a bare amount > 1, a conditional rule's when, and the null-guard gate's reach into the nested then and otherwise predicates;
    • CONTROL: valid, guarded predicates, at the top level and nested, are clean at the door and at the build;
    • PARITY: for each refused body, the door's findings equal the build's;
    • the differential: a stored sibling's broken rule is not this write's to answer for.
  • The fence (enumeration pin): in packages/lint/src/runtime-gate.object-formula-writes.test.ts. The fenced body now carries one site for each of the three passes still fenced: a requiredWhen, an option visibleWhen, and an action visible. Before this PR it carried no option visibleWhen, so it named only two of the three. The body also carries the lifted validation-rule site. The build flags all four sites. The object door flags the lifted site alone. On an object write, runStackExpressionPasses returns exactly the build's own findings for the admitted passes.
  • Protocol door: a new 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 block in packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, through the real saveMetaItem and publishMetaItem:
    • (a) both card bodies are refused with a 422 INVALID_METADATA carrying the build's located finding, and nothing lands;
    • (a) the same refusal on a draft's promotion;
    • (b) a valid, guarded condition (record.amount != null && record.amount > 100) still saves, and the row lands;
    • (d) for each refused body, the door and os build give the same finding, compared on rule, where, path, message and hint.
  • The roster comment in runtime-gate.object-writes.test.ts is reworded. The roster itself does not move.

Reverse verification (one-off, from committed HEAD fcc1ae0c)

  • What was mutated. The fence was put back on the validation-rule loop with scripts/ablation-replace.mjs: for (const rule of recordsOf(validations)) became for (const rule of objectWrite ? [] : recordsOf(validations)). The anchor was hit once, 1 → 0, and the blob went 55ee216f3d48 → 61fe6c01789b.
  • Rebuild and dist proof. @objectstack/lint was rebuilt. ablation-dist-preflight found the planted marker in 4 built files.
  • The lint suites (source): red as predicted, 7 failed and 10 passed. Red: the 4 LIT tests, PARITY, and the two fence tests. Green: CONTROL, the differential, the registry test, the build-flags-each-site test, and the six formula: the metadata save door stores a formula that calls an unregistered function (sqrt), and every read answers null with nothing logged — the docs say the shared validator backs metadata registration #22019 formula-door tests.
  • The protocol block (dist-mediated): red as predicted, 4 failed and 1 passed. Red: (a) for both bodies, (a) on promotion, and (d). Green: (b).
  • Restore. The tool restored the file: blob 55ee216f3d48 equals HEAD, and git diff HEAD is empty. Lint was rebuilt, and --absent found the marker gone from all 14 built files, with a clean tree. Both suites went green again: lint 17 passed, and the protocol file 79 passed.

Measurements

  • Corpus first (H4): the stop condition was not met. Every object this tree ships was judged by the build's validation-rule pass before the door changed. That is every *.object.ts under packages/** and examples/**, plus the two app-multi-package sub-stacks: 118 objects in 17 groups.
    • It was judged at the raw shape and at the ObjectSchema.parse shape.
    • After the change it was judged again by the door's own function (runRuntimeAuthoringRules, type object, with the object's own group as the context).
    • 21 rules carry 13 predicates on 10 objects:
      • examples: 11 predicates on 7 objects (app-crm 3 on 2, app-showcase 6 on 4, app-todo 2 on 1);
      • platform: plugin-security 2 on 2 (sys_position, sys_user_position), plus one rule on sys_user that carries no predicate.
    • Result: 0 build errors and 0 build warnings; 0 door errors and 0 door advisories.
    • The card's two bodies, run as a positive control in the same harness, gave 2 build errors and 2 door errors.
  • H1: confirmed. The fence is StackExpressionOptions.runtimeWriteType (validate-expressions.ts:1178). It is consulted in runStackExpressionPasses (:1222 at base, :1233 at head). At base the validation-rule loop read fieldFormulasOnly ? [] : recordsOf(validations) (:1859). The lift removes that guard. Passes 2 to 4 keep theirs: the field walk's early continue, and the action loop.
  • H2: confirmed. No registry change. The entry declares runtimeTypes: ['flow', 'action', 'hook', 'object'] (authoring-rules.ts:602 at base). runtimeAuthoringRulesFor('object') (runtime-gate.ts:550) already dispatched it.
  • H3: confirmed. The door's verdict equals the build's finding. This is pinned key by key through the real save path for both card bodies. At the lint level it is pinned for four bodies, including when and the nested null-guard sites.

Clause-② (measured)

Tests and gates (all at fcc1ae0c)

  • Package tests:
    • @objectstack/lint: 121 files, 5646 tests passed.
    • @objectstack/metadata-protocol: 219 files passed and 3 skipped; 28055 tests passed and 19 skipped.
    • typecheck passed for both. The lint run includes its test-typecheck. --listFiles shows both new and edited test files inside the tsc program.
  • Consumer readings. These are the files in the door's consumer radius that carry validation-rule fixtures or drive the object save door, run against a rebuilt closure:
    • @objectstack/rest: meta-object-extension-property-classes, meta-object-materialization-agreement, meta-object-overlay-extension-fold, meta-object-owd-gate and meta-publish-package-scope. 5 files, 71 tests passed.
    • @objectstack/objectql: save-meta-response-conformance, publish-meta-response-conformance and plugin.integration. 3 files, 67 tests passed.
    • No other test fixture saves a validation predicate through the publish door. That was found by grepping every test with validations against the door's entry points.
  • Gates.
    • dispatch-gates.mjs --commands was derived at this head: 63 commands, all exit 0. --ran reconciles: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN.
    • check:dual-build-cjs-loads first answered PREREQUISITE NOT MET. It passed after a full turbo build.
    • check:type-check-debt was cut off by the batch's time cap and passed when re-run alone (92s).
    • The artifact-roster block (34 non-self-test families) and the 11 declared wide-population families were also run: 45 commands, all exit 0.
  • ESLint, narrowed to the 6 touched TypeScript files (--no-inline-config): 6 files, 0 errors and 0 warnings.
    • The count is read from --format json.
    • Each file is matched by eslint.config.mjs (--print-config), and no file was reported as ignored.
    • eslint.config.mjs enables no type-aware linting (no parserOptions.project), so this diff cannot move the verdict on any untouched file.

Acceptance notes

  • A gap that both doors share. The build's validation-rule pass judges only the top-level condition and when of each rule. A nested then or otherwise rule's condition gets the null-guard gate and nothing else.
    • Measured through the real saveMetaItem at this head: a conditional rule whose then.condition is sqrt(record.amount) > 1, and whose otherwise.condition is a bare amont > 1, saves with a 200, and the row lands. The same predicate at the top level is refused with a 422.
    • The door now mirrors the build exactly, so this is the build's gap. It is outside this card. It is reported on the card for the seat to file.
  • Docs. formulas.mdx was not edited. Its promise now holds at the object save door for formula fields and validation-rule predicates. The "Build-time validation" section could name the object save door: that is a docs addition, not a false line.
  • Contract review. Triage's grade asks for a contract review for each pass. It is not attached here. It is the seat's, from an isolated subagent at the contract-review tier.

Generated by Claude Code

claude added 3 commits October 6, 2026 21:55
…rdict

On an object write the expression rule's fence now admits the
validation-rule pass beside the field-formula pass: every validations[]
condition and when, with the null-guard gate over the nested then /
otherwise branches, judged at the build's own position in the walk. The
field-rule slots, option visibleWhen and the object's action predicates
stay fenced, and the fence pin now names one site of each.

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
…t save door

Through the real saveMetaItem and publishMetaItem: both card bodies are
refused with a 422 INVALID_METADATA carrying the build's located finding
(active save and draft promotion), a valid guarded condition still saves,
and the door's issue equals the build's finding key by key. The registry
comment records the corpus reading taken before the crossing.

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
…e predicate os build refuses

Claude-Session: https://claude.ai/code/session_01GV6oYwgc1kWiUCb1YaprQ7
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/l label Oct 6, 2026
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 4 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b220e0943e87d26256316387e3c484f3f50b8416 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from c50202c762323892ec02b3f010f89d7e0e5e62c4 — the merge of head fcc1ae0c4121532be3b4c335edb0e2323dcf2294 into base b220e0943e87d26256316387e3c484f3f50b8416, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c50202c762323892ec02b3f010f89d7e0e5e62c4 && git checkout c50202c762323892ec02b3f010f89d7e0e5e62c4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b220e0943e87d26256316387e3c484f3f50b8416 fcc1ae0c4121532be3b4c335edb0e2323dcf2294 && git checkout -B drift-repro b220e0943e87d26256316387e3c484f3f50b8416 && git merge --no-ff fcc1ae0c4121532be3b4c335edb0e2323dcf2294

node scripts/docs-audit/affected-docs.mjs --json b220e0943e87d26256316387e3c484f3f50b8416

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b220e0943e87d26256316387e3c484f3f50b8416 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fcc1ae0c4121532be3b4c335edb0e2323dcf2294
Local-runs: none

PR #22041 (card #22032, pass 1 of 4), head fcc1ae0c, base b220e094 (merge base 60ccda5a), 3 commits, 7 files (+450/−58). Inputs: the card's body and its four comments (grade 6024268378, unlock 6025250992, claim 6026005435, os-dev-report 6026974644), parent #22019 and merged PR #22031, the PR body, file list and net diff against main, the head's check-runs, and origin/main plus the head read into a private ref of this checkout. Nothing was built, run or re-run.

① Derived judgments

  1. The accept set narrows at exactly one seam, and the diff says where — RIGHT. packages/lint/src/validate-expressions.ts, runStackExpressionPasses: the validation-rule loop's guard fieldFormulasOnly ? [] : recordsOf(validations) becomes recordsOf(validations) (head :1874). On an object write the rule now emits the build's validation-rule findings. Everything else in the function keeps its guard under the renamed local objectWrite: stack flows (:1526), the field walk's early continue after judgeFieldFormula (:1988, which keeps the field-rule slot loop :1995 and the per-option visibleWhen loop :2007–:2033 off the door), stack actions (:2181), object actions (:2184), sharing rules (:2198), hooks (:2213).
  2. Which object writes newly answer 422 — RIGHT, as the changeset claims. The one gate is assertRuntimeAuthoringRules in packages/metadata-protocol/src/protocol.ts, called from saveMetaItem in publish mode (:20410) and from promoteDraftForPublish (:21973); publishMetaItem (:21690) and publishPackageDrafts (:23057) both promote through that helper. So the active save, the draft promotion and the package draft publish all move from 200 to 422 INVALID_METADATA for a rule predicate the validator refuses; a draft save stays ungated (pinned: the draft save resolves, its promotion is refused). OS_ALLOW_UNLINTED_METADATA_WRITES=1 (runtime-authoring-gate.ts:117) still degrades the refusal to a logged write, as the changeset says. No code in packages/metadata-protocol moves; its door narrows through the @objectstack/lint dependency.
  3. What is newly refused, and at what severity — RIGHT. Per rule: condition through check(where, rule.condition, objectName, 'record', undefined, true) (traversal hydration on, so record.account.status in the showcase invoice object is served, not refused); a conditional rule's when through check(…' when', …); and checkNullGuards (severity error) over rulePredicates(rule, ''), which recurses into then / otherwise and labels a nested site validation rule 'R' then → 'C'. check warnings ride as advisories through the registry entry's severity: i.severity ?? 'error' mapping, unchanged. The changeset's enumeration of refused shapes (unknown function, undeclared field, bare reference, syntax error, unguarded nullable ordering, has() no guard) is the build's own list for this pass.
  4. The door's verdict equals the build's — RIGHT, by construction and by pin. The registry entry (authoring-rules.ts) is unchanged in code: one run body maps where, path, message and hint the same way for runAuthoringRules('build') and the runtime gate; the lifted loop runs at the build's own position in the object walk (validations before the field walk in both modes), so order is the build's too. Pinned key by key (rule, where, path, message, hint) through the real saveMetaItem for both card bodies in protocol.runtime-authoring-gate.test.ts (d), and by toEqual(atBuild) for four bodies (unregistered call, bare reference, when, nested null-guard) in runtime-gate.object-validation-writes.test.ts PARITY, each non-vacuous (the build refuses every one).
  5. Passes 2 to 4 stay fenced and the enumeration pin names them — RIGHT. runtime-gate.object-formula-writes.test.ts now carries one fault per fenced pass — field 'name' requiredWhen (pass 2), field 'tier' option 'gold' visibleWhen (pass 3), action 'fx_close' visible (pass 4) — beside the lifted validation 'amount_root' and a clean formula. The build flags all four sites; the door's error where list equals [LIFTED_SITE] exactly, its advisories are empty, and runStackExpressionPasses(stack, { runtimeWriteType: 'object' }) equals the build's findings filtered to the admitted sites. The base body carried no option visibleWhen, so before this PR the pin named two of the three fenced passes; the diff completes it, and the roster pin in runtime-gate.object-writes.test.ts moves in comment only.
  6. No public surface moves — RIGHT. runtime-gate.ts is untouched (git diff origin/main..head -- packages/lint/src/runtime-gate.ts is empty); runtimeTypes: ['flow', 'action', 'hook', 'object'] is unchanged; validateStackExpressions(stack) keeps its signature. StackExpressionOptions and runStackExpressionPasses are module exports that neither package entry re-exports (src/index.ts:66 exports validateStackExpressions, fieldRuleRootIssue, FIELD_RULE_BOUND_ROOTS and the ExprIssue type; src/runtime.ts:77 exports only AuthoringFinding and AuthoringSeverity from the registry), and tsup bundles .d.ts from those two entries, so the fieldFormulasOnly → objectWrite rename and the StackExpressionOptions docblock stay internal.
  7. The .d.ts-reaching text is true. AuthoringRuleContext is re-exported as a type from src/index.ts:951, so its runtimeWriteType docblock ships. The new sentence — an object write "is admitted for its field-formula pass and (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) its validation-rule pass alone" — is exactly what the guards in judgment 1 implement at head: two passes run under objectWrite, every other loop reads an empty list or continues. The pre-PR sentence ("field-formula pass alone") would have been false after the lift, so the correction belongs to this diff.
  8. The corpus claim is consistent with the diff and with a read of the tree. The diff pins the reading as a MEASURED comment on the registry entry: 21 rules carrying 13 predicates on 10 objects (examples 11 on 7 — app-crm 3 on 2, app-showcase 6 on 4, app-todo 2 on 1; platform: plugin-security 2 on 2, one predicate-less rule on sys_user). A read-only grep over *.object.ts under packages/** and examples/** on main finds exactly those 10 files carrying validations: and exactly 13 condition: / when: lines in the partition the comment gives (crm 1+2, showcase 3+1+2+0, todo 2, sys_user 0, sys_position 1, sys_user_position 1). Every one of the 13 is record.-qualified, calls only registered names (now, has, isBlank), and guards its nullable ordering operands with != null, so 0 refusals at the build and at the door is what the diff's own CONTROL and PARITY pins predict for such bodies. The changeset's "platform objects 2 on 3" and the PR body's "2 on 2 plus sys_user" are one reading. The 0-refusal count itself is the dev's measurement and is not re-run here.
  9. The nested gap is the build's, and the diff does not overclaim it. check() is applied to rule.condition and rule.when only; a nested then / otherwise condition meets checkNullGuards and nothing else, at the build and now at the door alike. The changeset, the StackExpressionOptions docblock and the .d.ts sentence all describe the nested reach as the null-guard gate, which is true. Parity with the build is this card's contract; the gap is a boundary flag (③).

② Semver level

.changeset/22032-object-save-door-validation-predicates.md: "@objectstack/lint": minor, "@objectstack/metadata-protocol": minor, title fix(lint)!: …, a BREAKING — what moves for consumers block naming the 200 → 422 move on the three paths of judgment ①2 with the remedy, and exactly one ADR-0087 marker, not-required (no-migration-prescription).

  • Level — RIGHT. The diff narrows a released package's runtime accept set; that is BREAKING, and this repository's level for a narrowing is minor with a BREAKING line (AGENTS.md Post-Task Checklist 3; the no-major changeset gate; triage's grade on the card). skip-changeset would be wrong: the published rule body changes and one .d.ts sentence moves. Check Changeset on the head: success.
  • @objectstack/metadata-protocol at minor with no source change — RIGHT. The door consumers call (saveMetaItem, publishMetaItem, publishPackageDrafts) is where the 422 is observed, so the BREAKING note belongs in that package's CHANGELOG; this is the form of the merged formula: the metadata save door stores a formula that calls an unregistered function (sqrt), and every read answers null with nothing logged — the docs say the shared validator backs metadata registration #22019 changeset, and every package here sits in one fixed group, so the level is uniform in any case.
  • Clause-②: line — RIGHT. PR body line 2 and the changeset both read Clause-②: no (narrowing); no yes, no (widening). The ADR-0087 disposition is argued on facts in the marker (no authorable key, export or stored shape moves; stored rows keep loading until next saved).

③ Boundary flags

From the os-dev-report 6026974644 (five deviations, two out-of-scope findings, open_questions: []) and the PR's acceptance notes:

  • D1, the AuthoringRuleContext.runtimeWriteType docblock — answered. Judged true in ①7, and within the claim's file surface ("the authoring-rule registry entry, only as far as the lift needs": a shipped sentence that would read false after the lift is that far).
  • D2, the fence pin gained the option visibleWhen site — answered. Right (①5): the claim asked that passes 2 to 4 stay fenced and pinned, and the base pin body named only two of the three.
  • D3, the contract review not attached by the dev — answered. This record is it.
  • D4, commit trailers — answered. AGENTS.md (Multi-agent discipline) requires the model-free pair and the pre-push hook refuses a model identifier; the three commits (24659a0b, 0d6b8ee1, fcc1ae0c) carry Claude-Session: and Co-authored-by: Claude with no model id. Conforms; the harness reminder's model-named trailer is subordinate to the repository rule.
  • D5, origin/main moved three commits past the base and was not merged — answered. 0db5ad5a (service-messaging read receipts), 0af4f661 (spec component-row anchors) and b220e094 (docs/qa checklist) touch neither packages/lint, packages/metadata-protocol nor any file in this diff; the PR reads mergeable: true; the queue merges main at landing. No action.
  • OOS-1, a nested then / otherwise condition is never handed to validateExpression by the build — escalated to the seat to file. Verified in the loop at head (①9). It is outside this card, whose contract is parity with the build, and the door now mirrors the build exactly; the dev's a-shaped evidence (a nested condition calling sqrt(record.amount) and a nested bare amont comparison publish with a 200 while the same predicate at the top level is refused) is a class (b) card of its own — the same formulas.mdx sentence, at os build first. Not a blocker for this PR.
  • OOS-2, formulas.mdx "Build-time validation" could name the object save door — answered. A docs addition noted in the PR's acceptance notes; no published line reads false (the promise now holds at the door for formula fields and validation-rule predicates). The docs-drift bot flagged content/docs/deployment/validating-metadata.mdx through the AUTHORING_RULES symbol; the entry moved comments only and the doc names the registry's runtimeTypes as the authority, which did not move — no re-verification owed.
  • Open questions — none declared, none found.
  • The release-train question on the claim (6026005435: before the last 17.x release or after the 18.0 opening) is not this review's question and nothing here rules on it; the PR is a draft and the seat holds the enqueue until it is answered.
  • Check-runs on the head, as read at 2026-10-06T23:07Z: 19 success (among them Check Changeset, Governed Surface Queue Guard, Type Check · source gates, Type Check · consumer gates, Type Check · debt ledger, Build Core, Dogfood Verify CLI, Dogfood Regression Gate (1/3)–(3/3), the claim, path and part-of guards), 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke, path-filtered), 9 in progress (Test Core (1/6)–(6/6), Lint & Repo Gates, Type Check · workspace, Temporal Conformance), 1 queued (Dogfood Regression Gate rollup). Recorded as read, not awaited; the in-progress families execute the new pins, and the landing leg still needs every one green — this record does not pre-empt them.

Implemented-by: claude/issue-22032-object-door-validation-predicates
Reviewed-by: session_01GV6oYwgc1kWiUCb1YaprQ7

VERDICT: PASS


Generated by Claude Code

This was referenced Oct 6, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 7, 2026 12:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 7, 2026 12:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 3d91885 Oct 7, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22032-object-door-validation-predicates branch October 7, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants