Repository navigation
fix(lint)!: the object save door gives the build's validation-rule verdict (#22032 pass 1) - #22041
Conversation
…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>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
Contract reviewServed-tier: PR #22041 (card #22032, pass 1 of 4), head ① Derived judgments
② Semver level
③ Boundary flagsFrom the os-dev-report
Implemented-by: VERDICT: PASS Generated by Claude Code |
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.mdxsays "the samevalidateExpressionvalidator backsos buildand 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 whosevalidations[].conditionwassqrt(record.amount) > 1, or a bareamount > 1, still saved with a 200, whileos buildrefused both at error.runStackExpressionPasses(packages/lint/src/validate-expressions.ts) no longer empties the validation-rule loop on anobjectwrite. On that write the rule now runs two passes, each at the build's own position in the walk:condition, with the relationship-traversal checks, and aconditionalrule'swhen. It also runs thehas(x)reads as a null guard and is not one — a publish-time lint should reject un-guarded nullable comparisons in CEL predicates #4763 null-guard gate over every predicate the rule carries, including those in the nestedthenandotherwiserules.validateStackExpressionsentry already declaredobjectafter 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, soruntimeAuthoringRulesFor('object')already dispatched it.runtime-gate.tsis untouched. Inauthoring-rules.tsonly comments move: the entry's comment and theAuthoringRuleContext.runtimeWriteTypedocblock. The latter is the one line that reaches a built.d.ts.fieldFormulasOnlytoobjectWrite. The old name would have been false. Its docblock (StackExpressionOptions.runtimeWriteType) now names the two admitted passes and the three fenced ones.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.packages/metadata-protocol. Only its test file gains the door-level pins.Pins
packages/lint/src/runtime-gate.object-validation-writes.test.ts(new, 8 tests). It covers:sqrt(record.amount) > 1, a bareamount > 1, aconditionalrule'swhen, and the null-guard gate's reach into the nestedthenandotherwisepredicates;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: arequiredWhen, an optionvisibleWhen, and an actionvisible. Before this PR it carried no optionvisibleWhen, 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,runStackExpressionPassesreturns exactly the build's own findings for the admitted passes.packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, through the realsaveMetaItemandpublishMetaItem:INVALID_METADATAcarrying the build's located finding, and nothing lands;condition(record.amount != null && record.amount > 100) still saves, and the row lands;os buildgive the same finding, compared on rule, where, path, message and hint.runtime-gate.object-writes.test.tsis reworded. The roster itself does not move.Reverse verification (one-off, from committed HEAD
fcc1ae0c)scripts/ablation-replace.mjs:for (const rule of recordsOf(validations))becamefor (const rule of objectWrite ? [] : recordsOf(validations)). The anchor was hit once, 1 → 0, and the blob went55ee216f3d48→61fe6c01789b.@objectstack/lintwas rebuilt.ablation-dist-preflightfound the planted marker in 4 built files.sqrt), and every read answers null with nothing logged — the docs say the shared validator backs metadata registration #22019 formula-door tests.55ee216f3d48equals HEAD, andgit diff HEADis empty. Lint was rebuilt, and--absentfound 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
*.object.tsunderpackages/**andexamples/**, plus the twoapp-multi-packagesub-stacks: 118 objects in 17 groups.ObjectSchema.parseshape.runRuntimeAuthoringRules, typeobject, with the object's own group as the context).app-crm3 on 2,app-showcase6 on 4,app-todo2 on 1);plugin-security2 on 2 (sys_position,sys_user_position), plus one rule onsys_userthat carries no predicate.StackExpressionOptions.runtimeWriteType(validate-expressions.ts:1178). It is consulted inrunStackExpressionPasses(:1222at base,:1233at head). At base the validation-rule loop readfieldFormulasOnly ? [] : recordsOf(validations)(:1859). The lift removes that guard. Passes 2 to 4 keep theirs: the field walk's earlycontinue, and the action loop.runtimeTypes: ['flow', 'action', 'hook', 'object'](authoring-rules.ts:602at base).runtimeAuthoringRulesFor('object')(runtime-gate.ts:550) already dispatched it.whenand the nested null-guard sites.Clause-② (measured)
@objectstack/lint, one doc comment changes (AuthoringRuleContext.runtimeWriteType).StackExpressionOptionsandrunStackExpressionPassesare not in the built declarations. No exported signature moves..changeset/22032-object-save-door-validation-predicates.mdcovers@objectstack/lintand@objectstack/metadata-protocol:minor, BREAKING,fix(lint)!. It gives the remedy, and its ADR-0087 disposition isnot-required (no-migration-prescription). It follows 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's changeset form.Tests and gates (all at
fcc1ae0c)@objectstack/lint: 121 files, 5646 tests passed.@objectstack/metadata-protocol: 219 files passed and 3 skipped; 28055 tests passed and 19 skipped.typecheckpassed for both. The lint run includes its test-typecheck.--listFilesshows both new and edited test files inside the tsc program.@objectstack/rest:meta-object-extension-property-classes,meta-object-materialization-agreement,meta-object-overlay-extension-fold,meta-object-owd-gateandmeta-publish-package-scope. 5 files, 71 tests passed.@objectstack/objectql:save-meta-response-conformance,publish-meta-response-conformanceandplugin.integration. 3 files, 67 tests passed.validationsagainst the door's entry points.dispatch-gates.mjs --commandswas derived at this head: 63 commands, all exit 0.--ranreconciles: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loadsfirst answered PREREQUISITE NOT MET. It passed after a full turbo build.check:type-check-debtwas cut off by the batch's time cap and passed when re-run alone (92s).--no-inline-config): 6 files, 0 errors and 0 warnings.--format json.eslint.config.mjs(--print-config), and no file was reported as ignored.eslint.config.mjsenables no type-aware linting (noparserOptions.project), so this diff cannot move the verdict on any untouched file.Acceptance notes
conditionandwhenof each rule. A nestedthenorotherwiserule'sconditiongets the null-guard gate and nothing else.saveMetaItemat this head: aconditionalrule whosethen.conditionissqrt(record.amount) > 1, and whoseotherwise.conditionis a bareamont > 1, saves with a 200, and the row lands. The same predicate at the top level is refused with a 422.formulas.mdxwas 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.Generated by Claude Code