Uh oh!
There was an error while loading. Please reload this page.
docs(skills): state which record each validation rule type is judged against - #15046
Merged
Merged
Conversation
…against The rule-type table in skills/objectstack-data/rules/validation.md said what each type is for and when it fails, but never which record the predicate is evaluated on -- and the answer is not uniform across the six. An author who generalises "validations[] holds invariants" reaches for `format` to guard stored data and silently gets a write gate instead. Adds one column, "Judged against", each cell measured from evaluateRule's dispatch in packages/objectql/src/validation/rule-validator.ts: - script / cross_field -> ctx.merged, the prior row overlaid with the write - format / json_schema -> ctx.data, the write payload, behind a `field in data` guard, so an omitted field is never judged - state_machine -> ctx.data value against ctx.previous, same guard; the insert leg checks initialStates instead - conditional -> its `when` on ctx.merged, then the nested rule re-dispatched through the same switch Table only: no prose, heading, example or tool-choice changes. Net lines 0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
os-zhuang
approved these changes
Sep 4, 2026
os-zhuang
marked this pull request as ready for review
September 4, 2026 03:39
os-zhuang
enabled auto-merge
September 4, 2026 03:39
Uh oh!
There was an error while loading. Please reload this page.
os-zhuang
deleted the
claude/issue-15011-validation-table-judged-record
branch
September 4, 2026 04:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#15011
The rule-type table under "Available Rule Types" in
skills/objectstack-data/rules/validation.mdsaid what each type is for and when it fails, but never which record the predicate is evaluated on — and the answer is not uniform across the six. An author who reads "use ascriptrule for an invariant" and reasonably generalises it to "validations[]holds invariants" reaches forformatto enforce a stored-data invariant and gets a write gate instead: no error, no signal, and legacy rows never checked.One column added,
Judged against, one short cell per row. Table only — no new prose section, no new heading, no example block, no change to the tool-choice paragraph at the head of Script Validation (merged atc454640a), no second file. Net 0 lines; the column is added to the eight existing table lines.Before
After
Engine evidence, per row
Every cell is measured on this branch from
packages/objectql/src/validation/rule-validator.ts, not copied from the card. The context the dispatch hands out is built at:2000-2012:The one type annotation elided above is the plain record-map type; it is dropped only because a tag-shaped fragment does not survive this platform's body sanitizer.
RuleContext's own docblock (:271-275) states the split independently: "datais the raw incoming write (a PATCH on update);mergedoverlays it on the prior record … Field-scoped rules (state_machine,format,json_schema) key offdatato decide whether the write actually touched their field."evaluateRule's switch is at:2177-2191:evaluateRulescriptcheckPredicate(rule, ctx.merged, ctx.previous, …)checkPredicate:2332ctx.merged— the prior row overlaid with the write. Bound as CELrecord, withpreviousalongside.cross_fieldcaseasscript, same callcheckPredicate:2332ctx.merged, identicallyformatcheckFormat(rule, ctx.data, …)checkFormat:2384ctx.data— opensif (!(rule.field in data)) return null, then readsdata[rule.field], so an omitted field is not judgedjson_schemacheckJsonSchema(rule, ctx.data, …)checkJsonSchema:2451ctx.data— samefield in dataguard, thendata[rule.field]state_machinecheckStateMachine(rule, ctx.mode, ctx.data, ctx.previous, ctx)checkStateMachine:2209field in dataguard, thenfrom = previous[rule.field],to = data[rule.field]. Insert leg:data[rule.field]againstrule.initialStates, no prior row.conditionalcheckConditional(rule, ctx)checkConditional:2499whenonctx.merged(record: ctx.merged, previous: ctx.previous), thenevaluateRule(branch, ctx)re-dispatches the nested rule through the same switch with the samectx— so a nestedformatstill reads the payloadAll six agree with the reading the card predicted; no row had to be written against it.
Six rows is still the complete set.
ValidationRuleSchema(packages/spec/src/data/validation.zod.ts:351-359) is adiscriminatedUnionover exactlyScriptValidationSchema,StateMachineValidationSchema,FormatValidationSchema,CrossFieldValidationSchema,JSONValidationSchema,ConditionalValidationSchema— the sixz.literaltypes at:179, :194, :211, :293, :314, :545. No seventh type, so the "There is no other type" sentence under the table stands untouched.Ratchet and size readings
Both readings the published-skills contract asks for, lines and tokens (
ceil(utf8 bytes / 4), the ratchet's own unit):rules/validation.md— linesrules/validation.md— tokensskills/**, all 46.md— linesskills/**, all 46.md— tokensSKILL.mdalone — lines / tokensThe
SKILL.mdsum is unmoved because the edited file is arules/file, not aSKILL.md; the all-.mdrow is the reading that actually moves and is reported beside it rather than instead of it.Ceiling untouched. The gate's own line, after the change:
+102 of the 610 tokens of headroom, leaving 508. No ceiling edited, no lines bought — so nothing is owed to the "delete content to pay the ratchet" rule.
The sibling line ratchet was run to confirm it does not cover this path, and it does not — its population is
.claude/**plus the two repo-root instruction files:Gates
Family re-derived after the commit from the real changeset —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— which named 20 families for this path; the dispatch list named those 20 pluscheck:skill-docs. The union of 21 was run. Verification run head:09802409, tree clean, diff exactly one file (8 insertions, 8 deletions).Each exit code was captured before any pipe; each line below is the gate's own verdict text.
check-ci-filter-parityOK: all 135 declared cross-package glob(s) (95 unique) are covered by \core` or `crosspkg`, every `crosspkg` entry still covers one, and the `test` job's `if:` still names both filters.`check-closing-keyword-paritycheck-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8180 tracked file(s), all registered).check-comment-mask-corpus✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 5833 files, 0 disagree, 0 unparseable, 42.4s (comparator self-test: 12 cases pass).check-cross-package-test-inputs(node)OK: 25 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check-shard-attestation✓ check-shard-attestation: 2 aggregate gate(s) count 3 declared leg(s) across 3 attesting job(s).check-skills-token-ratchet✓ check-skills-token-ratchet: 36 authored bundle file(s) within their ceilings; 11 generator-owned file(s) measured, not ratcheted.check-test-completenesscheck:doc-formula-expressions✓ check:doc-formula-expressions (spec TSDoc, #6763): 9 @example(s) judged clean across 1137 packages/spec/src files — rls-predicate=8, hook-record-condition=1, record-formula=0; 0 exempt.check:skill-docs✅ Skill docs in synccheck:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 432 file(s) · 5850 bare \--` token(s) · 1390 launcher-rooted run(s) · 9 separator(s) JUDGED · 5 vitest-backed script name(s) derived from 81 manifest(s)`check:corpus-claim-driftcheck-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.check:cross-package-test-inputs(pnpm)All 117 self-test cases passed./OK: 25 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:doc-authoring✓ doc authoring guard: 46 published skill files clean — no internal issue-id references.check:nul-bytescheck-nul-bytes: OK (scanned 8173 text file(s) -- 8173 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).check:pm-governed-merges✓ check-governed-merges --self-test: 245 assertions (…)/live: the real generator declared 9 output(s) and certified this tree— the enumeration in the middle is elided here for length onlycheck:refd-timer-probeOK check-refd-timer-probe: 5828 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.check:role-wordcheck-role-word: OK, no new occurrences of the reserved word.check:skill-compatibility✓ check-skill-compatibility-version: 11 SKILL.md file(s) reconciled against 79 workspace packagescheck:skill-frame-sync✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 filescheck:skill-identifier-livenesscheck-skill-identifier-liveness OK — Leg 1: 465 citation(s) over 46 published file(s) checked against 94464 implementation word tokens (3 ledgered exemption(s)); Leg 2: 8 registered exhaustive section(s), 0 ledgered gap(s).check:watch-hint-literal✓ check-watch-hint-literal: 48 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree.20 green, 1 NOT MEASURED.
check-test-completenessexited 3, which is that gate's prerequisite code, not a finding's 1. Its own text:check:doc-formula-expressionsfirst exited 3 the same way (PREREQUISITE NOT MET — the workspace package @objectstack/formula is not built, then the same for@objectstack/lint). That one was fixable locally:pnpm exec turbo run build --filter=@objectstack/formulaand--filter=@objectstack/lint, after which it was re-run and produced the real green quoted above. It is reported as measured;check-test-completenessis not, because nothing local can produce the log it grades.Heavy runs went through
scripts/pm/os-verify-lock.sh(slotissue-15011): the 21-gate sweep held the lock 75s, waited 0s.Scope and landing
Governed surface (
skills/**) — opened as a draft, no reviewers requested, auto-merge not armed, ready not flipped; the dispatching seat reviews in-seat and requests the approvers, and the landing is the maintainer's.skip-changeset: this diff publishes nothing from any package — it changes one Markdown file inside the published skills catalog, which ships as documentation rather than as a released package artifact. Same reading the recent skills PRs on this path used.Generated by Claude Code