Skip to content

fix(spec)!: the filter save and validate doors refuse a dotted addDays offset - #26655

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-v03a57
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-v03a57

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread
Fixes #26601

Clause-②: no (narrowing) — the save and validate doors refuse a dotted addDays $field that every SQL read already refused. Nothing is widened.
Metadata-protocol: narrowed (a filter, a dataset filter or a dataset measure filter may no longer carry an addDays offset whose $field is a dotted path)
Skills-docs: none — skills/objectstack-query/rules/filters.md already teaches the offset as a number field of the same record and says SQL push-down refuses a dotted offset; this moves that refusal to save time and adds no new authoring rule. Moving "dotted" into that page's refused-at-400 list is a one-word follow-up for a skills PR (governed surface, kept out of this one).
Ruling-ref: 6415710193 (letter A on #25808), seat ruling 6439436943, claim 6439488124. Scope is the author-time half only; B′ (an offset through a to-one path) is not in scope.

维护者速读

  • 改了什么: 过滤条件里 addDays 偏移量若写成带点的跨表路径(如 contract_type.review_sla_days),现在在保存与 os validate 时就被拒(INVALID_FILTER / 400),措辞与 SQL 读取时的拒绝一致;同时把数据集度量过滤器读取时被隐去的原因一并带出。
  • 为什么改: 以前 os validate 通过、看板每次读取都 400;内存求值器沿路径读到的是关联记录的 id,偏移按 0 天算,静默算错。裁决 A 已把偏移限定为同一记录的数值列。
  • 风险与回滚: 收窄(BREAKING):带点偏移的已存过滤器在保存时会被拒,但这些过滤器在任何 SQL 部署上本来就读不出来。回滚 = revert 本 PR。
  • 先看: packages/spec/src/data/filter-comparand-type.ts(judgeAddDaysOffset、dottedAddDaysOffsetRefusal)、packages/spec/src/data/filter.zod.ts(AddDaysReferenceSchema 的 regex 与 addDaysOffsetMessage)、.changeset/26601-dotted-add-days-offset-refused.md。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/data/filter.test.ts -t 26601,预期 7 个用例全绿,其中数据集度量过滤器与 ObjectStackDefinitionSchema 均在 measures.1.filter.review_started_at.$lt 处被拒。
  • 需要你做的: 无,席位落地。

What changes

  • The shared comparand walk (filter-comparand-type.ts, normalizeFilterComparandTypes) still steps around a { $field } reference and a well-formed $now anchor, except for one part: an addDays offset whose $field is a dotted path is refused at every scalar comparison with INVALID_FILTER / 400. The reason is driver-sql's own sentence for the same refusal (sql-driver-cross-field-comparison.ts:352 and :517): the addDays offset "contract_type.review_sla_days" is a dotted path, and SQL push-down compiles same-table column references only (no relation traversal, no alias-qualified columns). The walk runs at the FilterConditionSchema save door (data filters, dataset filter, dataset measure filter), in parseFilterAST, and at the engine's where, aggregations[i].filter and having doors.
  • The offset schema (filter.zod.ts, AddDaysReferenceSchema) refuses a dotted $field with the same reason. That covers FieldReferenceSchema.addDays, NowAnchorSchema.addDays and the ordering slots of FieldOperatorsSchema / ComparisonOperatorSchema. It is a regex, not a refine, so the published JSON Schema carries the rule as a pattern and the dropped-refinement ledger does not grow.
  • The withheld 400 on a dataset measure filter. Measured: the measure filter reaches the engine's aggregations[i].filter door, where assertAggregationFilterIsEvaluable (having-filter.ts) refuses cross-field rules in where's withheld words, with no author-provenance restore. The walk runs before it (engine.ts near line 20463), so a dotted offset there is now refused by the walk with the path and reason named. No executor file changed. The other cross-field refusals at that door stay withheld (acceptance note below).
  • Review 6438749334's two should-fixes (the now-anchor extra-key refusal): the "there is no subDays" clause prints only when subDays is among the undeclared keys, so a one-key refusal of any other key keeps its last sentence inside the 500-character client bound; the stale isNowAnchor docblock sentence ("refused … by each executor at query time") is trimmed to name the walk.
  • Superseded readings, named per the seat: the An ADR-0021 dataset measure cannot express a deadline that is another column plus an offset held in a third column — the grace-aware on-time rate has no spelling #14104 ruling-record correction 5550346132 ("answered by the memory evaluator, refused by SQL push-down") and the spec pin at filter.test.ts:1972 (the dotted nested reference accepted "exactly as $field itself allows"). Reason: ruling A on Analytics datasets: no filter compares a datetime column against now minus an SLA held on a related record #25808 bounds the offset to a same-record column, and the only executor that walked the path, the formula evaluator, reads a lookup's id on a stored row and counts the offset as zero days. The pin now asserts the refusal.
  • Docs: content/docs/protocol/objectql/query-syntax.mdx loses "A dotted offset … validates" in both places it said so; content/docs/references/data/filter.mdx regenerated for the new describe.
  • Not changed: the reference's own $field (a dotted base reference keeps its asymmetry: memory walks it, SQL refuses it), the bare implicit form { field: { $field, addDays } } (an operator spec named $field, whose fate is unchanged), and every executor.

Executors measured before the change (origin/main c8ed4c9)

Tests

  • packages/spec/src/data/filter.test.ts: a [#26601] block. The schemas refuse at addDays.$field; the four ordering slots on both operator copies; FilterConditionSchema at all six scalar comparisons and under $and / $or / $not; DatasetSchema measure filter and filter at the slot; ObjectStackDefinitionSchema (the schema os validate parses) at datasets.0.measures.1.filter.review_started_at.$lt, with a same-record control through the same door; controls for a same-record offset and a dotted base reference. The :1972 pin flipped; the ordering-slot pin at :1986 now uses a same-record offset.
  • packages/spec/src/data/filter-comparand-type.test.ts: the walk refuses on the pair, the anchor and the resolved anchor at all six comparisons, with code, status and the driver-sql sentence; under combinators and both parseFilterAST forms; the measure-filter position stays under 500 characters with its last sentence intact; controls kept by reference. The subDays clause is pinned both ways, plus a one-key refusal under $and with the engine prefix inside 500 characters.
  • service-analytics (tests only): now-anchor-dataset.test.ts, cross-field-offset-dataset.test.ts and include-relation-cross-field-boundary.test.ts asserted that the dotted offset validates at the schema door. They now assert the refusal, and they keep their read-time pins by putting the dotted filter back past the save door. The bare dotted arm still validates and is still refused by the driver.

Acceptance notes

  • Other withheld refusals at aggregations[i].filter (out of scope, no card): an undeclared, non-numeric or tenant offset column in a dataset measure filter is still answered in where's withheld words by assertAggregationFilterIsEvaluable, with no author-provenance restore. A dataset measure filter is the author's own, so a provenance mark there would restore those reasons; that is a design question for the analytics lane, not a pass-through in a file this PR touches.
  • Disclosure at the engine doors: the walk's refusal names the field path and the offset, as the walk's other refusals at the same doors already do (malformed anchor, comparand type). A row-level read scope cannot carry an addDays (the CEL compiler emits none), so no policy text reaches this sentence.
  • Base $field asymmetry kept: { $lte: { $field: 'duty.due_on' } } still validates; memory walks it and SQL refuses it. Same reasoning would apply there, but it is outside this card's order.
  • Formula evaluator called directly still walks a dotted offset (matches-filter-now-anchor.test.ts:69). Every in-repo caller sits behind a door that runs the walk.

Verification

Package suites ran on b9c396e87; the merge of origin/main that made c7f0010ef brought no file in spec, service-analytics, objectql, formula, plugin-security, lint or driver-memory. Dogfood and the derived gates ran on c7f0010ef. Every command ran under scripts/pm/os-verify-lock.sh, with the exit code captured before any pipe.

Package suites and typecheck
  • @objectstack/spec test: Test Files 730 passed, Tests 22073 passed, 1 todo. Typecheck exit 0.
  • @objectstack/service-analytics test: 191 files, 4647 passed, 282 skipped. Typecheck exit 0.
  • @objectstack/objectql test: 472 files, 8795 passed. Typecheck exit 0.
  • @objectstack/formula test: 54 files, 1452 passed.
  • @objectstack/plugin-security test: 229 files, 4962 passed, 45 skipped.
  • @objectstack/lint test: 166 files, 7389 passed.
  • @objectstack/driver-memory test: 72 files, 1758 passed.
  • driver-sql / driver-sqlite-wasm / driver-mongodb not re-run: none of them imports the walk or the offset schema, and no file of theirs changed.
  • pnpm --filter @objectstack/spec check:generated --fix, then plain: "All 14 generated artifacts are up to date" (only references/data/filter.mdx moved).
Dogfood (owed for the narrowing arm) at c7f0010

pnpm --filter @objectstack/dogfood test after the @objectstack/dogfood^... closure build: VERDICT command-exit 0, Test Files 301 passed and 3 skipped (304), Tests 2501 passed and 18 skipped (2519).

Derived gates at c7f0010

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gave 106 commands, run serially with exit codes recorded. 105 exit 0. check:skill-examples first exited 3 (client SDK not built); after building @objectstack/client-react... it exited 0 with "266 prose examples type-check". check:dual-build-cjs-loads exits 3 (PREREQUISITE NOT MET, it needs the whole workspace built) and is left to CI. --ran reconciliation: "106 derived famil(ies) accounted for — 105 run, 1 NOT-MEASURED".

Reverse verification, both legs restored by blob hash
  • Walk leg: dottedAddDaysOffset made to answer undefined (ablation-replace.mjs, anchor 1 to 0, blob 48b975f2 to edc0bfb4). 6 failed / 231 passed across the two spec files. Every red is a walk or save-door pin: the scalar comparisons, the combinators, the length bound, the FilterCondition door, the dataset doors and the validate door. The schema-level pins stayed green. Restored: blob equals HEAD, git diff HEAD empty.
  • Schema leg: the AddDaysReferenceSchema pattern made vacuous (blob 2403f93a to 34ee3e03). 3 failed / 234 passed: the flipped :1972 pin, the two schemas, and the ordering slots. The walk pins stayed green. Restored the same way.
Engine probe at the measure-filter door (objectql dist, after the change)

engine.aggregate('clm_contract', { aggregations: [{ function: 'count', filter: { review_started_at: { $lt: { $now: true, addDays: { $field: 'contract_type.review_sla_days' } } } } }] }) answers INVALID_FILTER / 400: aggregate('clm_contract'): Filter comparand at aggregations[0].filter.review_started_at.$lt is refused: the addDays offset "contract_type.review_sla_days" is a dotted path, and SQL push-down compiles same-table column references only …. The same measure with addDays: { $field: 'review_sla_days' } answers [{"over":1}].


Generated by Claude Code

…idate doors

The comparand-type walk and the addDays offset schema now refuse an
addDays { $field } naming a dotted path, on the { $field, addDays } pair
and on the $now anchor, in driver-sql's own same-table words. A data
filter, a dataset filter and a dataset measure filter fail at save and
at os validate instead of on the first SQL read.

Also takes the two should-fixes from the contract review of the
now-anchor extra-key refusal: the subDays clause prints only when
subDays is among the undeclared keys, and the isNowAnchor docblock
no longer claims each executor refuses a malformed anchor.

Claude-Session: https://claude.ai/code/session_01WGufzeyqyEtWpnqFTkcC9r
Co-Authored-By: Claude <noreply@anthropic.com>
… characters or more

Claude-Session: https://claude.ai/code/session_01WGufzeyqyEtWpnqFTkcC9r
Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/protocol/objectql/query-syntax.mdx (via subDays (literal, a string literal in malformedNowAnchorRefusal))
What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 55 of 207 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 — 147 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 dbf7e04adff66aba72e634c65aae754440a8d89d → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e2e30243cc3e812cb65ca84e0363e4dc3358d79b — the merge of head c7f0010efadca7f13953892ba37c139351f87209 into base dbf7e04adff66aba72e634c65aae754440a8d89d, 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 e2e30243cc3e812cb65ca84e0363e4dc3358d79b && git checkout e2e30243cc3e812cb65ca84e0363e4dc3358d79b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin dbf7e04adff66aba72e634c65aae754440a8d89d c7f0010efadca7f13953892ba37c139351f87209 && git checkout -B drift-repro dbf7e04adff66aba72e634c65aae754440a8d89d && git merge --no-ff c7f0010efadca7f13953892ba37c139351f87209

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

⚠️ 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 dbf7e04adff66aba72e634c65aae754440a8d89d → 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: c7f0010efadca7f13953892ba37c139351f87209
Local-runs: none
Scope: packages/spec/src/data/filter-comparand-type.ts, packages/spec/src/data/filter.zod.ts + packages/drivers/driver-sql/src/sql-driver-cross-field-comparison.ts (the dotted-offset refusal at lines 270, 352 and 517), packages/objectql/src/engine.ts (the aggregations[i].filter door near line 20456 and the having door near line 20539), packages/objectql/src/having-filter.ts (assertAggregationFilterIsEvaluable), packages/formula/src/matches-filter.ts (resolveDayOffset, getPath), packages/plugins/plugin-security/src/rls-compiler.ts (the walk at line 413), packages/spec/src/data/filter.test.ts, packages/spec/src/data/filter-comparand-type.test.ts, skills/objectstack-query/rules/filters.md

Inputs read: card #26601 (body and all three comments: the seat ruling 6439436943, the claim 6439488124 and the os-dev report 6440958464), the governing records it names (maintainer ruling 6415710193 and the workload reading 6414128401 on #25808, the #14104 ruling 5507665791 and its correction 5550346132, the #15103 ruling 5548479553, and the prior contract review 6438749334 on #26592), PR #26655 (body, file list, net diff against main at this head) and the check-runs on the head. The rest of the diff (the changeset, the three service-analytics test files and the two docs pages) is the lane seat's ACCEPT at TIER_DEFAULT.

Check-runs at 2026-10-27T11:44Z. Complete and green: Lint & Repo Gates and every Lint lane, Governed Surface Queue Guard, Check Changeset, Spec property liveness, Dogfood Regression Gate, Type Check source gates, Build Docs, Build UI Deps, Test UI, Check PR Size, the PR-body guards and the docs link check. Still running: Build Core, all eight Test Core shards, Type Check workspace, Type Check consumer gates, Type Check debt ledger, Type Check UI and Build & E2E UI. Nothing had failed. The Temporal, opt-in and matrix-shard lanes are skipped, as this diff does not reach them. This record judges the design. The landing still waits for every required check to finish green, as the tier rule says.

① Derived judgments

  1. Accept-set change at the shared walk. judgeLiteralComparand now routes a { $field } reference and a well-formed now-anchor through judgeAddDaysOffset, which refuses when addDays is an object whose $field is a string containing a dot. Every other reference and anchor is still kept by reference. Read in the live tree, the predicate ref.includes('.') is the same one driver-sql uses at all three of its refusal sites, so the door refuses exactly the set the SQL read refuses. Right.

  2. Which doors this reaches. parseFilterAST runs the walk on everything it returns, and the FilterConditionSchema save door goes through it, so data filters, dataset filter and dataset measure filter are covered. The engine runs the walk at the where door, at aggregations[i].filter (line 20456) and at having (line 20539). The RLS compiler runs it at line 413. The dev's door list is right, and one door short in its own favour again: the RLS seam refuses the shape too. No policy can carry an addDays today because the CEL compiler emits none, so the extra door changes nothing for a real caller. Right.

  3. Order at the measure-filter door. At engine.ts the walk (line 20456) runs before assertAggregationFilterIsEvaluable (line 20457). So the dotted case is now answered by the walk, with the path and reason named, before the withheld sentence. That closes the card's "reason withheld" half without touching an executor. Right.

  4. Accept-set change at the schema. AddDaysReferenceSchema.$field gains a regex refusing any dot, with the SQL sentence as its message. FieldReferenceSchema.addDays and NowAnchorSchema.addDays both use that schema, and the four ordering slots of ComparisonOperatorSchema and FieldOperatorsSchema use those two, so the refusal reaches every slot the pair and the anchor are legal in. $eq and $ne admit any comparand at the operator schema and are judged by the walk at the FilterCondition door. Right.

  5. The issue path and message. In zod 4, when exactly one union option fails without aborting, that option's issues pass straight through. For a dotted offset the number option aborts on type and the object option fails only the regex, so the regex issue surfaces at addDays.$field with the custom sentence. The tests pin exactly that path. Right. A consequence is noted in ④.

  6. JSON Schema projection. The rule is a regex, so the published JSON Schema carries it as a pattern and the dropped-refinement ledger does not grow. No committed artifact carries this schema: the json-schema manifest shards hold no addDays, the authorable-surface key set is unchanged (data/FieldReference:addDays already present), and no export is added or renamed, so api-surface does not move. The one describe change is regenerated into content/docs/references/data/filter.mdx. Right.

  7. Executors unchanged and consistent. driver-sql already refuses the shape at lines 352 and 517. having-filter.ts keeps its own class rules for the offset column. The formula evaluator's resolveDayOffset walks the path with getPath; on a stored row where the lookup holds an id string, getPath returns undefined at the second segment, the offset reads as zero days, and the comparison runs. I confirmed that reading in the code. So before this change the only executor that accepted the shape answered it wrong on a stored row. Refusing it at every door restores the An ADR-0021 dataset measure cannot express a deadline that is another column plus an offset held in a third column — the grace-aware on-time rate has no spelling #14104 ruling's own premise that the offset evaluates identically on both paths. Right.

  8. The base $field keeps its asymmetry. A dotted base reference such as { $field: 'duty.due_on' } still validates everywhere, memory walks it and SQL refuses it. The PR names this, pins it as a control, and leaves it outside the card's order. Right, and the right carrier.

  9. Public surface. dottedAddDaysOffset, dottedAddDaysOffsetRefusal, judgeAddDaysOffset, dottedAddDaysReference and dottedAddDaysOffsetSentence are module-private. The refusal reuses INVALID_FILTER / 400. Right.

  10. The two should-fixes carried from review 6438749334 are taken: the subDays clause prints only when subDays is among the undeclared keys, and the stale isNowAnchor docblock sentence now names the walk. Both are pinned. Right.

② Semver level

@objectstack/spec: minor. Right.

  • Metadata-protocol: narrowed is measured: FieldReferenceSchema, NowAnchorSchema, the ordering slots, FilterConditionSchema, DatasetSchema and ObjectStackDefinitionSchema all accepted addDays: { $field: 'a.b' } on base, and each refuses it now. A filter is something a metadata author writes. AGENTS.md Post-Task Checklist step 3 makes a declared kind at least minor on the package the diff moves.
  • Clause-②: no (narrowing) matches the diff. Nothing widens. The PR body and the changeset carry the same value and the same arm.
  • The BREAKING banner, the fix(spec)!: title and one ADR-0087 marker reading not-required (no-migration-prescription) are present in the changeset body. The disposition is sound: no key is added, removed or renamed, and there is no mechanical rewrite that keeps what the author meant, because the related value has to be copied onto a column of the row. The changeset states the remedy. Check Changeset is green on the head.
  • service-analytics, objectql, formula and the docs pages change tests or prose only and publish nothing. No second changeset owed.

③ Boundary flags

④ Design judgment

packages/spec/src/data/filter-comparand-type.ts

The scenario: an author writes a dataset measure filter whose addDays offset reads contract_type.review_sla_days, runs os validate, and gets green gates and a tile that errors on every read. After this change, ObjectStackDefinitionSchema refuses the stack at the slot, in the sentence the SQL read would have used. I tried to break it along these lines.

  • Predicate agreement. The walk, the schema and driver-sql all ask whether the string contains a dot. There is no shape the walk refuses that an executor would have compiled, and none it keeps that SQL refuses on this rule. Good.
  • Both carriers. The pair and the anchor share judgeAddDaysOffset, and the anchor's well-formed check runs first, so a malformed anchor still gets its own sentence and a well-formed one with a dotted offset gets this one. The resolved anchor form the engine hands the executors is judged the same way and pinned. Good.
  • Copy-on-write contract. The new branch answers keep or refuse and never narrows, so a kept reference is still the same object. The control test pins it for five shapes. Good.
  • Refusal length. The sentence at the measure-filter position under the engine prefix, with a long field and a long offset path, is pinned at or under 500 characters with the "NOT applied" tail intact. preview bounds the offset path at 40 characters. Good.
  • Non-string $field on the offset. { addDays: { $field: 123 } } is kept by the walk and refused by the schema's own union sentence at the save door. Same as before this change. Not a finding.
  • Empty $field on the offset. addDays: { $field: '' } passes the regex and the walk. On base, z.string() accepted it too, so this is not a regression; SQL refuses it as an undeclared column and memory reads it as zero days. Outside this card. Not a finding.

No must-fix.

packages/spec/src/data/filter.zod.ts

  • The regex on AddDaysReferenceSchema.$field is the right instrument: it projects to JSON Schema and keeps the refinement ledger flat. Its custom message is the SQL sentence plus the same-record prescription, and the tests pin that the message carries no tracker number. Good.
  • The docblock at the top of the file now states the same-record bound and names the retired reading in one place; the ordering-slot docblock repeats it in one sentence. Consistent with the walk's module note. Good.
  • should-fix: the new dotted arm at the top of addDaysOffsetMessage is not reached by any pinned path. Under zod 4's single-option pass-through, a dotted offset surfaces as the regex's own issue at addDays.$field, and fieldReferenceIssueMessage reparses through FieldReferenceSchema and returns that same issue. The union's custom error is consulted only when no option or more than one option fails without aborting, which a dotted offset does not produce. Either pin a case that reaches the arm, or drop it and let the regex sentence be the one source. Not a must-fix: no door answers wrong, and the surfaced message is the right one.
  • The two spellings of the sentence, the walk's and the schema's, differ in case and punctuation only and are both pinned against the driver's wording. Acceptable, and the docblock says why there are two.

No must-fix. The tree does what the seat ruling dispatched: a dotted offset the runtime refuses is refused at save and validate time, with the reason named, and nothing is widened.

Implemented-by: claude/project-thread-v03a57
Reviewed-by: session_01WGufzfhobWEgUUrjz4rrbh

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-27T11:49Z

Head c7f0010efadca7f13953892ba37c139351f87209. Card #26601 (seat ruling 6439436943, under maintainer ruling 6415710193 letter A), claim 6439488124, dev report 6440958464.

  • Contract review: owed at CONTRACT_REVIEW_TIER, because filter-comparand-type.ts and filter.zod.ts are touched and the card is a narrowing. PASS 6441128827 names this head. A dotted addDays $field, on the pair form and on the $now anchor, is now refused at every author-time door: the save door, os validate, data, dataset and dataset measure filters, and the engine and RLS walks. It uses the same predicate and sentence as the SQL read. The measure-filter door now names the reason before the withheld sentence. The only executor that answered the shape, formula, answered it wrong on a stored row (0 days).
  • Should-fixes, not carried: (1) the dotted arm at the top of addDaysOffsetMessage is not reached under zod 4's single-option pass-through. The regex issue surfaces instead and carries the right sentence. Drop it or pin it on the next touch of filter.zod.ts. (2) The prose pointer under the An ADR-0021 dataset measure cannot express a deadline that is another column plus an offset held in a third column — the grace-aware on-time rate has no spelling #14104 correction is posted (#14104 seat note). It narrows that record's boundary sentence only, and its same-table example stands.
  • B′: not in this PR. Whether to reopen it is with the maintainer. If B′ is chosen, this refusal is the line it widens.
  • Shape: Fixes #26601, 10 files. '@objectstack/spec': minor with a BREAKING banner and one ADR-0087 marker (not-required (no-migration-prescription)). Clause-②: no (narrowing) and Metadata-protocol: narrowed on the body and the changeset. The claim left the kind to measurement. check-governed-merges --pr 26655: NOT governed, 453 changed lines.
  • Gates: at this act, 28 check-runs have succeeded, 7 were skipped and 0 are red. Ten are still running: Build Core, the Test Core shards and the Type Check lanes. Auto-merge waits for them.
  • Landing: Tier S. pr_ready + automerge_enable in this act.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 11:50
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 11:50
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 02b1392 Oct 27, 2026
48 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-v03a57 branch October 27, 2026 12:33
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 protocol:data size/m tests tooling

Projects

None yet

2 participants