Skip to content

feat(spec,core,objectql,driver-sql,formula,driver-mongodb,service-analytics): now-anchored filter comparand { $now: true, addDays } shifted by days held on the record - #26261

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-pgsc75
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-pgsc75

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #25808

Clause-②: yes (widening)
Metadata-protocol: added
Ruling-ref: 6415710193

What

A filter comparand anchored at the current instant and shifted by whole days read off the record — the maintainer's letter A on #25808, comment 6415710193:

// opened more than review_sla_days_offset days ago — the column holds NEGATIVE days
{ opened_at: { $lt: { $now: true, addDays: { $field: 'review_sla_days_offset' } } } }
// closed on or after thirty days ago
{ closed_on: { $gte: { $now: true, addDays: -30 } } }

{ $now: true, addDays: <integer | { $field }> } is legal exactly where a { $field } reference is: the whole comparand of $eq / $ne / $gt / $gte / $lt / $lte, on a date or datetime column. addDays is REQUIRED (the unshifted instant stays the {now} placeholder); its grammar is FieldReferenceSchema.addDays' — an integer of any sign or a { $field } to a numeric column of the same record. There is no subDays: "now minus the SLA" needs a column holding negative days, the sign rule the ruling names ("sign rules as addDays").

Work items 1–5 of the ruling

  1. Spec shape + authorable surface — NowAnchorSchema in data/filter.zod.ts, in the ordering and equality comparand unions beside FieldReferenceSchema; refused by name as an $in / $nin member and a $between endpoint; an equality triple with an anchored comparand lowers to the explicit $eq (as [spec] parseFilterAST lowers ['a', '=', { $field: 'b' }] to a bare field spec no backend reads as equality #7597 did for a reference). The FilterCondition door (comparand-TYPE walk, filter-comparand-type.ts) steps around a well-formed anchor and refuses a malformed one in the anchor's words; the lowering face (filter-lowering.ts) treats an anchored comparison as NULL-total. authorable-surface/data.json, json-schema.manifest/data.json, the api surface and content/docs/references/data/filter.mdx regenerated.
  2. Engine resolution + class rule — resolveFilterTokens (@objectstack/core, where the resolver lives) pins $now to the tree's one instant as an ISO string, so every executor sees { $now: '<ISO>', addDays }; an anchor without addDays or with a malformed offset is INVALID_FILTER / 400 there. The temporal-comparand door steps around the anchor as it steps around a reference. having-filter.ts judges the anchor on aggregations[i].filter (operands withheld, diagnostic to the log — where's posture) and on having (named — the author's own query): no base column, the target date / datetime, the offset declared and numeric, never the tenant column (on SQL), and compares it per row through the formula evaluator. No engine.ts edit was needed: the judges it calls are the ones that grew.
  3. driver-sql on all three dialects — applyNowAnchorComparison: the instant bound in the target column's storage form (datetime keeps the time of day — canonical ISO, mysqlDatetimeLiteral on MySQL, cast(? as timestamptz) / cast(? as datetime(3)); date meets the instant's UTC calendar day, cast(? as date); SQLite reads the canonical text directly), shifted through the existing crossFieldOffsetExpr — so NULL offset = 0 days through the same coalesce, pinned and documented. Predicate total: (A is not null and A op Bx) for the orderings and $eq, (A is null or A <> Bx) for $ne, so $not takes exactly the complement. Offset column under the 2026-08-06 rules; non-temporal target, malformed anchor and every other position refused with the operands withheld (finding: after the #7598 Q1=B ruling, a read scope with a driver-refused field reference answers 400 from the driver — which cuts across #5367's attribution argument on that one path #7929) and the diagnostic in the server log.
  4. Formula + one driver-mongodb cell — matchesFilterCondition resolves the anchor in the form of the stored value it meets (a bare calendar day stays a calendar day), $now: true reads the evaluator's clock for a caller with no resolver seam; MongoDB refuses the anchor in every position (INVALID_FILTER / 400), one pinned cell per position.
  5. Conformance, lint, docs — CROSS_FIELD_NOW_CASES (12 expectations × both temporal pairs + 4 per-class cells with a NOW off the fixture's time of day — 28 cases) and CROSS_FIELD_NOW_REFUSALS (14) on the offset fixture, driven on both paths by driver-sql (sqlite + live PostgreSQL locally; MySQL is the Temporal Conformance battery on main), driver-sqlite-wasm, service-analytics (where + RLS read scope + dataset measure filter + dataset filter, authored $now: true), plus formula, objectql engine (where / aggregation filter / having), spec, core and lint cells. content/docs/protocol/objectql/query-syntax.mdx gains "Comparing Against Now, Shifted by Days Held on the Record" after "Comparing Two Fields".

Item 6 (hotcrm's dataset authoring) is a hotcrm card, not this PR.

NativeSQLStrategy

Declines a query carrying an anchor as it declines a { $field } comparand (findCrossFieldComparand detects $now too), so a dataset filter or measure filter routes to the engine path where the driver compiles it under the 2026-08-12 ruling; the ObjectQL strategy's equality lowering emits the explicit $eq for an anchored comparand. A dataset's authored $now: true is pinned by the service's own token seam (resolvedDatasetScopeGetter).

Acceptance notes

  • Sign caveat. The ruling's title says "now minus an SLA"; the ruled shape has no subtraction. A column that means "days before now" must hold negative values (review_sla_days_offset: -14). The docs section and the schema description say so; a positive SLA column needs a negated sibling column or a formula field.
  • Related record. The ruling names the SLA "held on a related record"; SQL push-down compiles same-table columns only (2026-08-06), so a dotted offset validates, resolves in memory and is refused by SQL push-down by name — the same asymmetry the addDays pair has, pinned on both faces. The shape that answers everywhere is the SLA propagated onto the row.
  • driver-memory (not on the claim's surface): measured with a probe — it answers [] for a { $field } comparand today and answers the same [] for an anchor; that silent hole is pre-existing and reported as out of scope.

Verification

See the os-dev-report comment on #25808 for the test and gate ledger.

Generated by Claude Code

…lytics): now-anchored filter comparand shifted by days held on the record

A filter comparand `{ $now: true, addDays: <integer | { $field }> }`, the
maintainer's letter A on #25808 (ruling comment 6415710193): legal exactly
where a `{ $field }` reference is, as the whole comparand of $eq / $ne /
$gt / $gte / $lt / $lte on a date or datetime column. `addDays` is required
and shares `FieldReferenceSchema`'s grammar (there is no subDays; "now minus
the SLA" is a column holding negative days). The unshifted instant stays
the `{now}` placeholder.

- spec: `NowAnchorSchema` in the ordering and equality comparand unions;
  refused by name as a set member or `$between` endpoint; the comparand
  type door and the lowering face step around a well-formed anchor and
  refuse a malformed one in its own words; authorable surface, json-schema
  manifest, api surface and the filter reference page regenerated.
- core: `resolveFilterTokens` pins `$now` to the tree's one instant as an
  ISO string; a missing or malformed addDays is INVALID_FILTER / 400.
- objectql: the temporal door skips the anchor; `aggregations[i].filter`
  and `having` judge it by `where`'s rules and compare per row through the
  formula evaluator.
- driver-sql: same-table push-down on SQLite, PostgreSQL and MySQL through
  `crossFieldOffsetExpr` (NULL offset = 0 days), the instant bound in the
  target column's storage form; NULL-total predicates so `$not` is the
  exact complement; every other position refused with operands withheld.
- formula: `matchesFilterCondition` resolves the anchor in the stored
  value's form, so the memory path answers the SQL path's rows.
- driver-mongodb: refuses the anchor in every position (INVALID_FILTER).
- service-analytics: NativeSQLStrategy declines the anchor as it declines a
  `{ $field }` comparand, so dataset filters route to the engine path.

Pinned on the drivers' cross-field conformance corpus (CROSS_FIELD_NOW_CASES
/ CROSS_FIELD_NOW_REFUSALS) on both execution paths and on the analytics
dataset shape; changeset minor on @objectstack/spec.

Clause-②: yes (widening)
Metadata-protocol: added
Ruling-ref: 6415710193

Claude-Session: https://claude.ai/code/session_019YLF9PyrRqUX4N7C2pzSoH
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 7 package(s): @objectstack/core, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/formula, @objectstack/objectql, @objectstack/service-analytics, @objectstack/spec, touching 66 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/drivers/driver-sql/src/index.ts, packages/spec/api-surface/data.json, packages/spec/authorable-surface/data.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

18 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 3d50a43bcb55053e0501d939b304242b41cfde19.

⛔ 8 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/drivers/driver-sql/src/index.ts, packages/spec/api-surface/data.json, packages/spec/authorable-surface/data.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: organization_id (literal, 38 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 152 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 3d50a43bcb55053e0501d939b304242b41cfde19 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 3d50a43bcb55053e0501d939b304242b41cfde19

⚠️ 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 3d50a43bcb55053e0501d939b304242b41cfde19 → 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: 9594f3737bf2cf0f4e2dd815372fe13d85e724bd
Local-runs: none
Scope: packages/spec/src/data/filter-comparand-type.ts, packages/spec/src/data/filter-lowering.ts, packages/spec/src/data/filter.zod.ts + packages/spec/src/data/filter.test.ts, packages/core/src/utils/filter-tokens.ts, packages/objectql/src/having-filter.ts, packages/objectql/src/temporal-comparand-door.ts, packages/drivers/driver-sql/src/sql-driver.ts, packages/drivers/driver-sql/src/cross-field-conformance-cases.ts, packages/formula/src/matches-filter.ts, packages/drivers/driver-mongodb/src/mongodb-filter.ts, packages/services/service-analytics/src/comparand-shape.ts, packages/services/service-analytics/src/strategies/native-sql-strategy.ts, packages/services/service-analytics/src/strategies/objectql-strategy.ts, content/docs/protocol/objectql/query-syntax.mdx, .changeset/25808-now-anchored-comparand.md

Inputs read: card #25808 (body and all eight comments, ruling 6415710193 letter A, claim 6420309633, premise check 6420234055, os-dev-report 6423037096), PR #26261 (body, 35-file list, net diff against main at merge base 6b9355299, +2587 / -95), and the check-runs on the head. The rest of the diff (tests outside the files above, the regenerated spec artifacts, the strictness ledger, the pin ledger, the lint test, the sqlite-wasm and seam tests) is the lane seat's ACCEPT at TIER_DEFAULT. Head unchanged throughout the review.

① Derived judgments

  1. NowAnchorSchema shape against the ruling — right. closedObject(z.object({ $now: z.literal(true), addDays: z.union([z.number().int(), AddDaysReferenceSchema]) }).strict()): a closed object, addDays REQUIRED (no .optional()), $now the literal true, the offset grammar the very AddDaysReferenceSchema the addDays pair declares (a bare { $field }, no offset-on-offset). That is { $now: true, addDays: int | { $field } } exactly as 6415710193 spells it, sign rules as addDays (negative subtracts, no subDays). Pinned in filter.test.ts: two offset arms, required addDays, literal-true flag (false / string / 1 / null refused), malformed offset (fractional, string, object without $field, non-string $field), closed (subDays and note refused). check:authorable-surface artifact gains data/NowAnchor:$now and data/NowAnchor:addDays; api-surface gains NowAnchorSchema and NowAnchor; the strictness ledger counts the new strict site; the ADR-0122 pin ledger moves 789 to 790 for an isomorphic alias. All regenerated, not hand-written.
  2. Accept-set by position — right. The anchor enters orderingComparandSchema's union ($gt/$gte/$lt/$lte) beside FieldReferenceSchema; $eq/$ne are open z.any() slots (array refused) so it is accepted there by construction; the comparand-type door keeps a well-formed anchor in those six slots. $in/$nin refuse an anchor member by name with a pointed message; $between refuses it because its endpoint union (number, Date, string) never admitted an object — refused, but by the generic union text, not by the anchor's own sentence (see ③ item 5). FilterConditionSchema passes an anchor in a list slot exactly as it passes a { $field } there (pre-existing parity, pinned as a control); every executor refuses the position by name (driver-sql nowAnchorPositionError on $in members, text patterns and the bare { field: { $now } } spelling; having-filter nowAnchorPositionError; mongodb every position).
  3. Comparand-type door (filter-comparand-type.ts) — right, with one comment inaccuracy noted in ③. isNowAnchor recognises $now === true (authored) or a string (the resolver's pinned ISO form), isWellFormedNowAnchor requires an integer or { $field: string } offset, and a malformed anchor is refused in the anchor's own words ({now} prescription, "neither an integer") rather than falling to the plain-object refusal. Necessary: without this hunk the door would refuse every anchor as a non-literal object and the schema would be unusable through FilterConditionSchema. Pinned (atDoor, fractional cases).
  4. Lowering face (filter-lowering.ts) — right. operatorIsNullTotal treats an anchored scalar comparison as NULL-total and isNegativePolarityOperator excludes an anchored $ne, so rule 3 adds no IS NOT NULL guard — matching driver-sql's emitted predicate (A is not null and A op Bx) for the orderings and $eq, (A is null or A dne Bx) for $ne, and the memory evaluator's two-valued answer. $not is the exact complement; the corpus row "$not of $lte with a COLUMN offset" partitions the fixture with the $lte row on both paths.
  5. Equality triple lowering — right. convertComparison lowers ['f', '=', anchor] to { f: { $eq: anchor } } as it does for a reference, so the AST form cannot read $now as an operator spec; pinned for all four = spellings. The analytics ObjectQL strategy's equals arm does the same.
  6. Engine resolution (core resolveFilterTokens) — right, and the deviation off the claim surface is necessary. The ruling's item 2 names resolveFilterTokens; it lives in @objectstack/core, not objectql, so the file list follows the code. hasFilterToken reports an unresolved anchor as a placeholder; resolveNowAnchor pins $now: true to the tree's one instant (pinned.now, the same now every {N_days_ago} resolves from) as an ISO string and keeps the offset for the executor; missing or malformed addDays is MalformedNowAnchorError (INVALID_FILTER, status 400). The literal arm is deliberately not collapsed to a string because the target column's class decides calendar-day versus instant — correct, and it is why the anchor stays an object past the resolver.
  7. Class rule in objectql (having-filter.ts) — right; no engine.ts edit needed. assertAggregationFilterReferencesAreDeclared (aggregations[i].filter, operands withheld, diagnostic to the log) and assertConditionIsEvaluable/assertNowAnchorResolves (having, named) enforce: no base column, target date/datetime, offset declared and numeric; compareWithNowAnchor evaluates per row through the formula evaluator with the value in storage form. temporal-comparand-door.ts steps around the anchor as it steps around a reference. The two judges are the ones engine.ts already calls at :21463 and :21546, so the claim surface's "engine.ts pair validation" is satisfied where the judges live.
  8. driver-sql push-down — right, and both standing rulings hold. applyNowAnchorComparison binds the instant in the target's storage form (cast(? as timestamptz) / cast(? as datetime(3)) with mysqlDatetimeLiteral / cast(? as date); SQLite reads the canonical text), shifts through the existing crossFieldOffsetExpr (so NULL offset = 0 days via the same coalesce on all three dialects), and refuses: a malformed anchor, a non-temporal target (numeric, time, text), an undeclared target or offset, the tenant-isolation column on either side, a dotted offset path (2026-08-06 same-table rule, inherited), a non-numeric offset column. All with operands withheld and the diagnostic in the log. $now: true reaching the driver without the resolver reads the driver's own clock, a string is parsed and a non-instant string refused ("not an instant"). 14 refusal cells and 28 case cells in CROSS_FIELD_NOW_CASES / CROSS_FIELD_NOW_REFUSALS, swept per dialect cell.
  9. Memory evaluator (formula) — right. resolveNowAnchor shifts in the form of the stored value it meets (a bare YYYY-MM-DD actual puts the anchor in calendar-day form, else the ISO instant), reuses resolveDayOffset (NULL column = 0 days, truncation) and addWholeDays; a malformed anchor resolves to NO_OFFSET_BASE and fails closed for every operator. The 18:30Z per-class corpus cells pin that the date pair and the datetime pair answer different sets on both paths.
  10. driver-mongodb — right and documented, not silent. carriesNowAnchor refuses the anchor in every position with unsupportedFilterError naming the feature and the remedy ({N_days_ago} or a literal instant), operands withheld; ten positions pinned. The refusal is stated in the schema docblock, the addDays description, the changeset and the query-syntax page.
  11. service-analytics routing — right, and the deviation is necessary for the ruling. The ruling text itself requires NativeSQLStrategy to decline the anchor as it declines { $field } (2026-08-12) so the engine path compiles it; without isNowAnchor in findCrossFieldComparand the native compiler would bind the anchor object as a literal and answer a wrong row set silently. The ObjectQL strategy's equals lowering is the twin of item 5. resolvedDatasetScopeGetter runs resolveFilterTokens over scope.filter and scope.measureFilters, so an authored $now: true in a dataset is pinned by the service before any strategy reads it. Dataset measure filter, dataset filter, grouped dataset and the dotted-offset refusal are pinned end to end under fake timers.
  12. Documentation of the two semantics the brief names — present. NULL offset = zero days is stated in the schema docblock, the addDays description, the driver docblock, the changeset and the query-syntax table ("now + NULL is now"); the NULL-target reading (fails orderings and $eq, satisfies $ne, $not the complement) likewise. The MongoDB refusal is in the same four places. The own-clock fallback for a resolver-less caller is stated in the driver docblock and the docs page.
  13. No path-valued offset slipped in. The anchor's offset reuses AddDaysReferenceSchema, whose $field string the addDays pair already admits dotted (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: "dot-path allowed exactly as $field allows it; SQL push-down applies the same same-table rule"). The anchor inherits exactly that declared asymmetry: validates, memory resolves it, SQL refuses by name — pinned on the corpus and on the analytics face — and no JOIN is planned anywhere. Letter A, not B-prime.

② Semver level

.changeset/25808-now-anchored-comparand.md: @objectstack/spec: minor; core, objectql, driver-sql, driver-mongodb, formula, service-analytics patch. The diff adds an authorable comparand shape to the published filter grammar (data/NowAnchor:* on the authorable surface, NowAnchorSchema on the api surface), so the kind is added and the level it owes is minor on the package that publishes the protocol — spec — which the changeset carries; the six executor packages publish no new authorable key or export of their own and take patch. Both declaration lines are present and identical in the PR body and the changeset body: Clause-②: yes (widening) and Metadata-protocol: added, plus Ruling-ref: 6415710193. No breaking marker, no ADR-0087 disposition owed (nothing removed or renamed). Check Changeset is green on the head. The Clause-②: line matches what the diff publishes.

Clause-②: yes (widening)

③ Boundary flags

The dev's os-dev-report (6423037096) lists four deviations, two out-of-scope findings and an empty open_questions. Each answered:

  1. Files outside the claim surface (core filter-tokens.ts, service-analytics comparand-shape.ts and the two strategies, spec filter-comparand-type.ts / filter-lowering.ts, driver-sql index.ts corpus export and seam test, sqlite-wasm conformance cell, the pin and strictness ledgers, regenerated artifacts) — each necessary for the ruling, judged above at ① items 3, 4, 6 and 11; the corpus export and the second sweep cell are what makes "pinned on both paths" true; the ledgers are gate-owned counters that move with the schema. Not a breach.
  2. No engine.ts edit — answered at ① item 7: the judges engine.ts calls are the ones that grew.
  3. MySQL cell not executed locally — accepted: the MySQL emission is pinned by the SQL-text cell on the PR; the live MySQL cell is Temporal Conformance (live PG + MySQL) on main's full battery, which does not run on a PR (skipped on this head, as expected). Read by the publish lane, not this review.
  4. Commit trailer in the repo's model-free form — compliant, no action.
  5. Prose overstatement, non-blocking, for the lane seat: the PR body and changeset say the anchor is refused "by name" as a $between endpoint. It is refused (the endpoint union admits no object) but by zod's generic union text, not the anchor's own sentence; the schema docblock's "refused in every list position" is the accurate claim. Also one comment in core filter-tokens.ts (isUnresolvedNowAnchor) says a $now: 'now' is "left to the comparand-type door's plain-object refusal"; the door in fact keeps a string $now as the resolved form, and a non-instant string is refused loudly by driver-sql, the having judge and mongodb, and fails closed in the memory evaluator. Behaviour is correct and pinned; the comment and the "by name" phrase are the only inaccuracies. A follow-up comment fix, not a blocker.
  6. Out-of-scope finding, driver-memory answers [] silently for a { $field } comparand and therefore for an anchor — pre-existing, measured by the dev on the merge base, off this card's surface. No card exists for it yet (no issue since 17:00Z names it). Escalated to the lane seat to file under the dev's dedupe string "driver-memory cross-field comparand silent empty", per Prime Directive 10. Not this PR's defect.
  7. Out-of-scope finding, the RLS write-check seam compares an anchor against a non-temporal column as written because matchesFilterCondition has no declaration map there — a boundary of the pre-existing { $field } seam, noted in the PR's acceptance notes with no behaviour claimed. Accepted as stated; the declared seams (where, having, driver-sql) refuse.
  8. Ordinary queue landing. Not governed (0 of 35 paths hit the register), 2682 changed lines under 3000, no fork, draft and un-armed at review time. After this PASS and the lane seat's ACCEPT the owning seat readies and arms it.

Gate verdicts on the head (48 check-runs, all completed): every one of the eight required contexts is success — Lint & Repo Gates, TypeScript Type Check, Test Core (and all eight shards), Build Core, Governed Surface Queue Guard, Type Check UI, Build & E2E UI; Lint UI is skipped (no ui scope on this diff, the expected-skips roster). Also green: Check Changeset, Check PR Size, Spec property liveness, Lint · docs & code guards, Type Check · source gates (the spec artifact gates), Dogfood Regression Gate, Build Docs, the three claim guards and filter. Skipped by design: Temporal Conformance (live PG + MySQL), the dogfood shards, Test UI shards, the two opt-in harnesses. No red to attribute to base: the Test UI 7/16 and 12/16 reds that main carried until #26231 did not run on this head at all. Nothing was built, run or re-run locally.

Implemented-by: claude/project-thread-pgsc75
Reviewed-by: session_01RyMY3prh9GRva1Tt37Di3i

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 2 · 2026-10-26T18:08Z · head 9594f3737bf2cf0f4e2dd815372fe13d85e724bd

  1. Shape. Draft, base main, Fixes #25808. 35 files, +2587/−95. check-governed-merges --pr 26261: NOT governed, 2682 changed lines (under 3000). mergeable_state clean.
  2. Premise and ruling. Maintainer ruling A (6415710193): { $now: true, addDays: integer | { $field } }, addDays required. The premise check 6420234055 corrected three paths only; the dev re-measured them. Letter A is implemented, not B′.
  3. Gates (check-runs pinned to this head). 48 runs: 41 success, 7 skipped, 0 failed. check-expected-skips --pr 26261: OK (exit 0).
  4. Contract review. PASS at CONTRACT_REVIEW_TIER on this head, comment 6423604143 (face schema; hits filter.zod.ts, filter-comparand-type.ts, filter-lowering.ts, and the deviations it judged necessary: core resolveFilterTokens, the spec sibling doors, the analytics decline).
  5. Remainder at TIER_DEFAULT (seat-read). driver-sql applyNowAnchorComparison: the offset column passes the same gates as the addDays pair (declared, numeric, same table, not the tenant column, no dotted path); the target column and offset column bind as identifiers (??), the instant and literal days bind as values (?), nothing from the filter is interpolated into SQL text. Each predicate is null-total, so $not is the exact complement. The objectql having/aggregation judges, the formula memory path, the mongodb refusal and the conformance corpus match the changeset's per-package lines.
  6. Prose checked against the diff (changeset). The seven per-package bullets hold against their hunks. Spec minor, six executors patch; Clause-②: yes (widening) and Metadata-protocol: added match the moved authorable-surface and api-surface shards.
  7. Non-blocking, recorded here. (a) The PR body says $between refuses the anchor "by name"; it refuses through its endpoint union with generic text. (b) The isUnresolvedNowAnchor docblock in packages/core/src/utils/filter-tokens.ts says a string $now falls to the door's plain-object refusal; the door keeps a string $now as the resolved form. Behaviour is right and pinned; only the comment is off.
  8. Out of scope. The dev's finding (driver-memory answers [] silently for a { $field } comparand, and so for the anchor) is the frozen gap of [finding] driver-memory's own reference matcher has no $field arm — a cross-field comparand (bare or with addDays) reaching it is presumably compared as a literal object rather than resolved or refused (grep reading, to be measured) #15104 (closed not planned under [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499's driver-memory freeze). A pointer with this evidence goes on [finding] driver-memory's own reference matcher has no $field arm — a cross-field comparand (bare or with addDays) reaching it is presumably compared as a literal object rather than resolved or refused (grep reading, to be measured) #15104; no new card.
  9. Landing. Ready plus auto-merge.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 18:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 18:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit be531f6 Oct 26, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-pgsc75 branch October 26, 2026 19:12
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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Analytics datasets: no filter compares a datetime column against now minus an SLA held on a related record

2 participants