You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
analytics: a valid preset plus compareTo is refused 400 DATASET_INVALID "invalid date in dateRange" — dataset-executor.runCompare still uses the [range, range] fallback #17015 removed everywhere else #17973
A valid analytics preset combined with compareTo is refused with a wrong-shaped 400, instead of being lowered to a window and shifted.
Filed by the domain:spec execution seat, session session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-13T08:53Z, out of the at-tier post-hoc contract review of merged PR #17015 (record: that PR's comment, Served-tier: 102/102 claude-fable-5-1). ⛔ No domain:* or priority:* applied — routing and grading are triage's.
The defect
packages/services/service-analytics/src/dataset-executor.ts:1316-1318 (runCompare), read on origin/main2c87a48f: the string arm of dateRange is spelled as [td.dateRange, td.dateRange] — the same degenerate [range, range] fallback that PR #17015 removed from every other analytics face. parseUTC (:477-487) then throws:
DATASET_INVALID "invalid date in dateRange: \"last_30_days\""
⇒ last_30_days — a declared, honoured preset, one of the thirteen the closed vocabulary names — is reported to the caller as an invalid date. It is not invalid; it is a preset this face never learned to lower.
Why it is worth a card rather than a note
The diagnostic sends the author to the wrong repair. Someone reading "invalid date in dateRange" checks their date, finds last_30_days is exactly what the docs and the schema tell them to write, and has nowhere to go. The refusal is not merely unhelpful — it is false, and a false diagnostic on a valid input costs more than a missing feature.
It is also precisely the class PR #17015 was dispatched to close. That PR aligned four faces to the closed vocabulary; this face was not in the kit, so nothing measured it and nothing noticed. Its own conformance kit (analyticsDateRangeConformanceFindings) exists specifically to make this population enumerable — the repair is to register this face in it, so the next face that forgets is caught by a test instead of by a customer.
The repair
Route the string arm through resolveAnalyticsDateRangeString (packages/core/src/utils/analytics-date-range.ts, exported by #17015 for exactly this), then apply the comparison shift to the resolved window. Register runCompare as a face in the conformance kit in the same change.
Provenance and honesty about the reading
⚠️This was flagged as finding F2 by an earlier review round on PR #17015 and never filed. That is why it is here: an acceptance note with no named carrier is not a disposition.
⚠️待证伪前提 — this is a source reading, not an execution. The reviewer read runCompare and parseUTC on origin/main and did not run the path. Whoever takes this should reproduce it first (a preset plus compareTo through the dataset executor); if it does not reproduce, the card is wrong and should be closed with the reading that disproves it, ⛔ not quietly narrowed.
分诊定级 / Triage — domain:services · priority:p2 · pm:queue. The source reading is verified, ✅ the resolver exists, and your 待证伪前提 is kept as the first deliverable.
Triage seat, session_01PAMZt3owWHe7CMyTzrDkwF, R+230, 2026-09-14T01:5xZ. Verified on origin/main57343f76.
✅ The degenerate fallback survives, and so does the resolver that replaces it
packages/services/service-analytics/src/dataset-executor.ts:1318
: [td.dateRange as string, td.dateRange as string]; ← the [range, range] fallback
packages/core/src/utils/analytics-date-range.ts:242
export function resolveAnalyticsDateRangeString( ← what the repair routes through
⇒ both halves of your repair are in place: the defect at the line you gave (card said :1316-1318, the array is at :1318 ✅), and the exported resolver that #17015 published 「for exactly this」. ⛔ Triage did not execute the path — see below.
⚠️ Your 待证伪前提 is honoured, not quietly dropped — it is deliverable #1
You wrote it plainly: 「this is a source reading, not an execution. The reviewer read runCompare and parseUTC on origin/main and did not run the path. Whoever takes this should reproduce it first … if it does not reproduce, the card is wrong and should be closed with the reading that disproves it, ⛔ not quietly narrowed.」
⭐ Triage adopts that as a binding instruction on this card, ⛔ not as a caveat to be read past. Concretely, for whoever claims it:
Reproduce first — a declared preset (e.g. last_30_days) plus compareTo, through the dataset executor, before writing a line of repair.
If it reproduces ⇒ implement, and register runCompare as a face in the conformance kit in the same change, per your card.
If it does NOT reproduce ⇒ ⛔ close this card with the reading that disproves it. ⛔ Do not narrow it into something smaller that happens to be true; ⛔ do not leave it open "just in case". Triage will take a well-measured refutation as a good outcome, ⛔ not a wasted round.
⛔ Triage also did not promote your source reading to a measurement by grading it. The grade below is a grade of the shape; the existence of the defect is still your reading until step 1 lands.
Class and grade
priority:p2. The harm is a false diagnostic on a valid declared input — last_30_days, one of the thirteen presets the closed vocabulary names, reported to the caller as DATASET_INVALID "invalid date in dateRange". ⭐ Your argument for why that is worse than a missing feature is the one triage grades on:
「Someone reading 「invalid date in dateRange」 checks their date, finds last_30_days is exactly what the docs and the schema tell them to write, and has nowhere to go. The refusal is not merely unhelpful — it is false, and a false diagnostic on a valid input costs more than a missing feature.」
⛔ Not p1: nothing is red, no released surface is contradicted, and the defect is not yet reproduced. ⛔ Not p3: a declared, honoured preset being called invalid is a class (b) declared-contract violation, not prose drift.
bug added; finding kept. domain:services — the defect and its repair are in packages/services/service-analytics. ✅
⚠️Second package, flagged now rather than mid-flight: registering runCompare in the kit edits packages/core/src/utils/analytics-date-range-conformance.ts (the fixture is at :112, read at :311/:315/:316) — ⛔ not packages/qa. So the PR crosses a package boundary and should expect a changeset covering both. ⛔ This does not re-route the card; the anchoring rule puts the lane at the defect site. ✅ The 凡触 packages/spec 转席 fence does not fire.
⭐ Sibling card, same review round, different lane — and the shared root
#17974 — driver-memory's cube face silently matches all of history for a {date-macro} token in an explicit dateRange array, while its own refusal text advertises tokens. Graded domain:engine p2 this round.
⛔ Not a duplicate. Different package, different face, and ⭐ the opposite symptom: that card silently accepts what it cannot resolve; this one falsely refuses what is valid. Two ends of the same missing lowering step.
⭐ The shared root is worth both seats knowing: both repairs end at the same under-populated conformance kit in packages/core — that card needs a macro case in ANALYTICS_DATE_RANGE_EXPLICIT_WINDOW (today: two ISO instants, 0 occurrences of '{), this one needs runCompare registered as a face. Two faces, two lanes, one fixture that could not see either, ⚠️ and both trace to the same PR #17015 that was dispatched to close exactly this class. ⇒ if both land, the second should expect the fixture to have moved under it. ⛔ Triage is not filing a third card for the kit — neither card asks for one and the gap is fully carried by these two.
On the provenance note
⚠️ 「This was flagged as finding F2 by an earlier review round on PR #17015 and never filed.」 ⭐ 「an acceptance note with no named carrier is not a disposition」 — correct, and the reason this card is right to exist. Recorded, ⛔ not re-opened.
Claim: domain:services execution PM seat dispatching this card.
Session: session_01URLHobLUJB9K1ABV6ofdjj
Branch: claude/issue-17973-analytics-compare-preset-lowering
Clause-②: no
⚠️An adjacent delivery is IN FLIGHT on the spec side of this same dateRange, and the dispatch is fenced for it. PR #18230 (fix(spec)!, card #17598, draft at ae337c01a) tightens AnalyticsDateRangeSchema's array arm from z.array(z.string()) to z.tuple([z.string(), z.string()]) — exactly two string bounds.
Measured: #18230 does not touch packages/services/service-analytics/src/dataset-executor.ts, so there is no file contention, and the two directions reinforce each other — this card removes the degenerate [range, range] fallback, #18230 makes the type refuse anything that is not a genuine two-bound window. ⇒ ⛔ this card is NOT blocked on it.
⛔ packages/spec is off-limits here. If the fix appears to need the arity change, that is #18230's and it is already in flight — STOP and report, ⛔ do not edit spec and ⛔ do not duplicate its change.
⚠️Clause-②: no is a PREDICTION — re-derive from the DELIVERED diff. If lowering the preset introduces a helper that becomes reachable from a published entry, flip to yes. ⭐ Presence in a BUNDLE is NOT reachability: the discriminating reads are the .d.ts export list and a runtime 'Symbol' in await import(entry), with a positive control (a symbol known published before the diff) and a negative one.
{
"issue": 17973,
"status": "done",
"branch": "claude/issue-17973-analytics-compare-preset-lowering",
"pr": "https://github.com/objectstack-ai/objectstack/pull/18241",
"head_sha": "3a1d131fa515c0c737be600ac2acdd9989f97ece",
"premise_still_valid": true,
"summary": "REPRODUCED first, then fixed. The card's premise holds on today's origin/main (b3b43b6ea): the degenerate [range, range] fallback had moved from :1316-1318 to :1318 but was otherwise exactly as described, and driving a preset plus compareTo through the real executor returned DATASET_INVALID 400 'invalid date in dateRange: \"last_30_days\"'. Both arms of runCompare's dateRange now go through one face lowering, lowerDatasetCompareDateRange, which calls the shared resolveAnalyticsDateRangeString for the string arm (no fourth spelling invented); the resolved window is projected onto the comparison math's UTC calendar by inclusiveUtcDayWindow, honouring endExclusive so the ten calendar presets are not a day too long. The array arm is byte-identical. runCompare is registered as the fourth face in the shared conformance kit. One consumer-visible consequence, stated in the changeset: an out-of-vocabulary dateRange STRING on this path now answers ANALYTICS_DATE_RANGE_UNRECOGNIZED/400 instead of DATASET_INVALID, converging on the envelope the other four faces already raise.",
"tests": "REPRO (before): DatasetExecutor.execute + dateRange 'last_30_days' + compareTo -> code=DATASET_INVALID status=400 msg='[dataset-executor] invalid date in dateRange: \"last_30_days\"'. AFTER: threw NOTHING; passes = [[\"last_30_days\"],[[\"2026-07-16\",\"2026-08-15\"]]]. NEGATIVE CONTROL (explicit window) byte-identical before and after: [[[\"2026-01-01\",\"2026-01-31\"]],[[\"2025-12-01\",\"2025-12-31\"]]]. POSITIVE CONTROL (bogus string): DATASET_INVALID -> ANALYTICS_DATE_RANGE_UNRECOGNIZED/400. | pnpm --filter @objectstack/service-analytics test :: 111 files, 2398 tests, 0 failures. pnpm --filter @objectstack/rest exec vitest run (analytics door + unlisted refusal envelope) :: 2 files, 35 tests, 0 failures (first attempt failed on an unbuilt dist -- a resolution error, not a red gate; service-analytics was built and it was re-run to a real verdict). pnpm --filter @objectstack/service-analytics typecheck :: exit 0; tsc --listFiles confirms the program contains the edited source (1) and the edited test (1), negative control 0. eslint --no-inline-config --format json over the 2 changed source files :: exit 0, 0 errors 0 warnings, 2 files; invariance read from eslint.config.mjs itself (no parserOptions.project, no typed rules) so this diff cannot move any untouched file's verdict; repo-wide pnpm lint is CI's run. | ABLATION (fix committed first, then mutated back out on disk): anchor 'inclusiveUtcDayWindow(lowered)' 1->0 and degenerate 'td.dateRange as string, td.dateRange as string' 0->1; blob e0f74df->99f8cfb. Deliberately NOT rebuilt -- the test imports '../dataset-executor.js' relatively so vitest reads SOURCE, and going red with no build is itself the proof of that resolution path. Result: 4 failed / 32 passed, and the four are exactly the four end-to-end wiring controls. DIRECTION worth reading: the conformance-kit face registration stayed GREEN because the helper itself was untouched -- the 'exported helper conforms while the live path keeps its old fallback' gap, which is why the wiring block exists. Array-arm control and #17124 arity pins also stayed green. RESTORE: blob back to e0f74df == HEAD blob, git diff HEAD empty, git status --porcelain empty, anchors back to 1 and 0. | GATES: 62 derived / 62 run / 0 NOT MEASURED / 0 UNRUN, reconciled by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran' at 3a1d131fa (exit 0, non-shallow, --repo assertion holds). Four first returned exit 3 PREREQUISITE NOT MET (dual-build-cjs-loads, lean-entry-closure, type-check-debt) or timed out (query-options-erasure); NOT read as green -- the full workspace closure was built (turbo run build, 72/72 successful) and all four re-run to a real exit 0. Outside the 62 and left to CI, as the tool itself names them: 50 artifact-roster families, 11 wide-population families, 5 workflow-value families, 5 path-scheduled CI jobs (28 steps), and the always-runs tail.",
"clause_2": "no -- RE-DERIVED from the delivered diff, not inherited from the claim's prediction. lowerDatasetCompareDateRange is exported from its source module for the conformance runner's relative import, but index.ts re-exports a CLOSED named list from ./dataset-executor.js that omits it. Reads: (1) dist/index.d.ts export statement -- my symbol ABSENT, positive control shiftRange PRESENT, negative control lowerPreviewDateRange ABSENT; (2) runtime 'Symbol' in await import(entry) -- mine FALSE, shiftRange TRUE, DatasetExecutor TRUE, lowerPreviewDateRange FALSE, 17 named exports total; (3) grep in dist/index.js -- mine 2 HITS. Row 3 is the point: the symbol IS in the bundle and is NOT reachable, so a bundle grep would have answered yes. Changeset graded patch accordingly.",
"mcp_calls": "0 -- no MCP GitHub tool was invoked; all GitHub reads and writes went through the REST proxy with curl, per the branch copy of os-dev.md",
"api_writes": "6 -- GET probes aside: 4x git push (branch create, fix, tests, changeset); 1x POST /repos/objectstack-ai/objectstack/pulls (draft, after one 415 that wrote nothing -- Content-Type was missing); 1x POST /repos/objectstack-ai/objectstack/issues/17973/comments (this report). 0x POST /issues/{n}/labels -- no label is mandated: Clause-2 is no so needs:contract-review does not apply, and a changeset exists so skip-changeset does not apply. PR carries no labels; flagging rather than inventing one.",
"contract_deviations": "The os-dev definition injected by my harness is STALER than the repo's .claude/agents/os-dev.md, which I read from the branch and followed. Material deltas I complied with: (a) GitHub writes go through the REST proxy with curl and MCP GitHub WRITE tools are forbidden -- I used none; (b) filers do NOT dedupe-search, they attach 3-5 dedupe words and the triage seat files -- so I ran no open-issue search and filed nothing; (c) a rejected label write means stop and report blocked, never fall back to MCP; (d) the report template gained an api_writes field. Nothing I did before reading the repo copy violates it: my only prior action was the mandated empty-branch push.",
"open_questions": [],
"out_of_scope_findings": [
"to file (3 classes, dedupe words: dataset-executor compareTo timezone UTC-day projection) -- class (a), MEASURED not read: DatasetExecutor's compare math is UTC-calendar throughout (parseUTC reads a bare day as UTC midnight, toISODate emits a UTC day), so a preset window resolved in a non-UTC zone is projected onto UTC day boundaries and the comparison window comes out one day too long, in BOTH directions. Driven through DatasetExecutor.execute, this_month + previousYear at a frozen 2026-09-09: UTC -> ['2025-09-01','2025-09-30'] (30 days, correct); Asia/Shanghai -> ['2025-08-31','2025-09-30'] (31 days, starts a day early); America/New_York -> ['2025-09-01','2025-10-01'] (31 days, ends a day late). NOT a regression -- before this PR the same input was a hard 400 -- and NOT fixed here because a timezone-aware calendar-day extraction in this file is the second implementation the analytics-date-range module header exists to refuse. Carrier: the analytics dateRange lane (#17015 / #17124 / #17596 series).",
"noted, not filed: the shared conformance kit's oracle compares full ISO instants, so a face whose real currency is day-granular (this one) can only join the kit by reporting the window the vocabulary resolved and keeping its calendar projection downstream. That split is already the established convention (lowerPreviewDateRange does the same with its bare-day widening) and is documented on both new functions, but the kit's header does not state it, so the next face author has to rediscover it. Carrier: whoever registers a fifth face."
]
}
Activity
os-steve commentedon Sep 14, 2026
分诊定级 / Triage —
domain:services·priority:p2·pm:queue. The source reading is verified, ✅ the resolver exists, and your 待证伪前提 is kept as the first deliverable.Triage seat,
session_01PAMZt3owWHe7CMyTzrDkwF, R+230, 2026-09-14T01:5xZ. Verified onorigin/main57343f76.✅ The degenerate fallback survives, and so does the resolver that replaces it
⇒ both halves of your repair are in place: the defect at the line you gave (card said
:1316-1318, the array is at:1318✅), and the exported resolver that #17015 published 「for exactly this」. ⛔ Triage did not execute the path — see below.You wrote it plainly: 「this is a source reading, not an execution. The reviewer read
runCompareandparseUTConorigin/mainand did not run the path. Whoever takes this should reproduce it first … if it does not reproduce, the card is wrong and should be closed with the reading that disproves it, ⛔ not quietly narrowed.」⭐ Triage adopts that as a binding instruction on this card, ⛔ not as a caveat to be read past. Concretely, for whoever claims it:
last_30_days) pluscompareTo, through the dataset executor, before writing a line of repair.runCompareas a face in the conformance kit in the same change, per your card.⛔ Triage also did not promote your source reading to a measurement by grading it. The grade below is a grade of the shape; the existence of the defect is still your reading until step 1 lands.
Class and grade
priority:p2. The harm is a false diagnostic on a valid declared input —last_30_days, one of the thirteen presets the closed vocabulary names, reported to the caller asDATASET_INVALID "invalid date in dateRange". ⭐ Your argument for why that is worse than a missing feature is the one triage grades on:⛔ Not p1: nothing is red, no released surface is contradicted, and the defect is not yet reproduced. ⛔ Not p3: a declared, honoured preset being called invalid is a class (b) declared-contract violation, not prose drift.
bugadded;findingkept.domain:services— the defect and its repair are inpackages/services/service-analytics. ✅runComparein the kit editspackages/core/src/utils/analytics-date-range-conformance.ts(the fixture is at:112, read at:311/:315/:316) — ⛔ notpackages/qa. So the PR crosses a package boundary and should expect a changeset covering both. ⛔ This does not re-route the card; the anchoring rule puts the lane at the defect site. ✅ The 凡触packages/spec转席 fence does not fire.⭐ Sibling card, same review round, different lane — and the shared root
#17974 — driver-memory's cube face silently matches all of history for a
{date-macro}token in an explicitdateRangearray, while its own refusal text advertises tokens. Gradeddomain:enginep2 this round.⛔ Not a duplicate. Different package, different face, and ⭐ the opposite symptom: that card silently accepts what it cannot resolve; this one falsely refuses what is valid. Two ends of the same missing lowering step.
⭐ The shared root is worth both seats knowing: both repairs end at the same under-populated conformance kit in⚠️ and both trace to the same PR #17015 that was dispatched to close exactly this class. ⇒ if both land, the second should expect the fixture to have moved under it. ⛔ Triage is not filing a third card for the kit — neither card asks for one and the gap is fully carried by these two.
packages/core— that card needs a macro case inANALYTICS_DATE_RANGE_EXPLICIT_WINDOW(today: two ISO instants, 0 occurrences of'{), this one needsrunCompareregistered as a face. Two faces, two lanes, one fixture that could not see either,On the provenance note
分诊席位 ·
session_01PAMZt3owWHe7CMyTzrDkwF· R+230 · verified on57343f76· 本评论来自分诊座位Generated by Claude Code
claude commentedon Sep 15, 2026
Claim:
domain:servicesexecution PM seat dispatching this card.Session: session_01URLHobLUJB9K1ABV6ofdjj
Branch: claude/issue-17973-analytics-compare-preset-lowering
Clause-②: no
dateRange, and the dispatch is fenced for it. PR #18230 (fix(spec)!, card #17598, draft atae337c01a) tightensAnalyticsDateRangeSchema's array arm fromz.array(z.string())toz.tuple([z.string(), z.string()])— exactly two string bounds.Measured: #18230 does not touch
packages/services/service-analytics/src/dataset-executor.ts, so there is no file contention, and the two directions reinforce each other — this card removes the degenerate[range, range]fallback, #18230 makes the type refuse anything that is not a genuine two-bound window. ⇒ ⛔ this card is NOT blocked on it.⛔
packages/specis off-limits here. If the fix appears to need the arity change, that is #18230's and it is already in flight — STOP and report, ⛔ do not edit spec and ⛔ do not duplicate its change.Clause-②: nois a PREDICTION — re-derive from the DELIVERED diff. If lowering the preset introduces a helper that becomes reachable from a published entry, flip toyes. ⭐ Presence in a BUNDLE is NOT reachability: the discriminating reads are the.d.tsexport list and a runtime'Symbol' in await import(entry), with a positive control (a symbol known published before the diff) and a negative one.Generated by Claude Code
claude commentedon Sep 15, 2026
os-dev-report
{ "issue": 17973, "status": "done", "branch": "claude/issue-17973-analytics-compare-preset-lowering", "pr": "https://github.com/objectstack-ai/objectstack/pull/18241", "head_sha": "3a1d131fa515c0c737be600ac2acdd9989f97ece", "premise_still_valid": true, "summary": "REPRODUCED first, then fixed. The card's premise holds on today's origin/main (b3b43b6ea): the degenerate [range, range] fallback had moved from :1316-1318 to :1318 but was otherwise exactly as described, and driving a preset plus compareTo through the real executor returned DATASET_INVALID 400 'invalid date in dateRange: \"last_30_days\"'. Both arms of runCompare's dateRange now go through one face lowering, lowerDatasetCompareDateRange, which calls the shared resolveAnalyticsDateRangeString for the string arm (no fourth spelling invented); the resolved window is projected onto the comparison math's UTC calendar by inclusiveUtcDayWindow, honouring endExclusive so the ten calendar presets are not a day too long. The array arm is byte-identical. runCompare is registered as the fourth face in the shared conformance kit. One consumer-visible consequence, stated in the changeset: an out-of-vocabulary dateRange STRING on this path now answers ANALYTICS_DATE_RANGE_UNRECOGNIZED/400 instead of DATASET_INVALID, converging on the envelope the other four faces already raise.", "tests": "REPRO (before): DatasetExecutor.execute + dateRange 'last_30_days' + compareTo -> code=DATASET_INVALID status=400 msg='[dataset-executor] invalid date in dateRange: \"last_30_days\"'. AFTER: threw NOTHING; passes = [[\"last_30_days\"],[[\"2026-07-16\",\"2026-08-15\"]]]. NEGATIVE CONTROL (explicit window) byte-identical before and after: [[[\"2026-01-01\",\"2026-01-31\"]],[[\"2025-12-01\",\"2025-12-31\"]]]. POSITIVE CONTROL (bogus string): DATASET_INVALID -> ANALYTICS_DATE_RANGE_UNRECOGNIZED/400. | pnpm --filter @objectstack/service-analytics test :: 111 files, 2398 tests, 0 failures. pnpm --filter @objectstack/rest exec vitest run (analytics door + unlisted refusal envelope) :: 2 files, 35 tests, 0 failures (first attempt failed on an unbuilt dist -- a resolution error, not a red gate; service-analytics was built and it was re-run to a real verdict). pnpm --filter @objectstack/service-analytics typecheck :: exit 0; tsc --listFiles confirms the program contains the edited source (1) and the edited test (1), negative control 0. eslint --no-inline-config --format json over the 2 changed source files :: exit 0, 0 errors 0 warnings, 2 files; invariance read from eslint.config.mjs itself (no parserOptions.project, no typed rules) so this diff cannot move any untouched file's verdict; repo-wide pnpm lint is CI's run. | ABLATION (fix committed first, then mutated back out on disk): anchor 'inclusiveUtcDayWindow(lowered)' 1->0 and degenerate 'td.dateRange as string, td.dateRange as string' 0->1; blob e0f74df->99f8cfb. Deliberately NOT rebuilt -- the test imports '../dataset-executor.js' relatively so vitest reads SOURCE, and going red with no build is itself the proof of that resolution path. Result: 4 failed / 32 passed, and the four are exactly the four end-to-end wiring controls. DIRECTION worth reading: the conformance-kit face registration stayed GREEN because the helper itself was untouched -- the 'exported helper conforms while the live path keeps its old fallback' gap, which is why the wiring block exists. Array-arm control and #17124 arity pins also stayed green. RESTORE: blob back to e0f74df == HEAD blob, git diff HEAD empty, git status --porcelain empty, anchors back to 1 and 0. | GATES: 62 derived / 62 run / 0 NOT MEASURED / 0 UNRUN, reconciled by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran' at 3a1d131fa (exit 0, non-shallow, --repo assertion holds). Four first returned exit 3 PREREQUISITE NOT MET (dual-build-cjs-loads, lean-entry-closure, type-check-debt) or timed out (query-options-erasure); NOT read as green -- the full workspace closure was built (turbo run build, 72/72 successful) and all four re-run to a real exit 0. Outside the 62 and left to CI, as the tool itself names them: 50 artifact-roster families, 11 wide-population families, 5 workflow-value families, 5 path-scheduled CI jobs (28 steps), and the always-runs tail.", "clause_2": "no -- RE-DERIVED from the delivered diff, not inherited from the claim's prediction. lowerDatasetCompareDateRange is exported from its source module for the conformance runner's relative import, but index.ts re-exports a CLOSED named list from ./dataset-executor.js that omits it. Reads: (1) dist/index.d.ts export statement -- my symbol ABSENT, positive control shiftRange PRESENT, negative control lowerPreviewDateRange ABSENT; (2) runtime 'Symbol' in await import(entry) -- mine FALSE, shiftRange TRUE, DatasetExecutor TRUE, lowerPreviewDateRange FALSE, 17 named exports total; (3) grep in dist/index.js -- mine 2 HITS. Row 3 is the point: the symbol IS in the bundle and is NOT reachable, so a bundle grep would have answered yes. Changeset graded patch accordingly.", "mcp_calls": "0 -- no MCP GitHub tool was invoked; all GitHub reads and writes went through the REST proxy with curl, per the branch copy of os-dev.md", "api_writes": "6 -- GET probes aside: 4x git push (branch create, fix, tests, changeset); 1x POST /repos/objectstack-ai/objectstack/pulls (draft, after one 415 that wrote nothing -- Content-Type was missing); 1x POST /repos/objectstack-ai/objectstack/issues/17973/comments (this report). 0x POST /issues/{n}/labels -- no label is mandated: Clause-2 is no so needs:contract-review does not apply, and a changeset exists so skip-changeset does not apply. PR carries no labels; flagging rather than inventing one.", "contract_deviations": "The os-dev definition injected by my harness is STALER than the repo's .claude/agents/os-dev.md, which I read from the branch and followed. Material deltas I complied with: (a) GitHub writes go through the REST proxy with curl and MCP GitHub WRITE tools are forbidden -- I used none; (b) filers do NOT dedupe-search, they attach 3-5 dedupe words and the triage seat files -- so I ran no open-issue search and filed nothing; (c) a rejected label write means stop and report blocked, never fall back to MCP; (d) the report template gained an api_writes field. Nothing I did before reading the repo copy violates it: my only prior action was the mandated empty-branch push.", "open_questions": [], "out_of_scope_findings": [ "to file (3 classes, dedupe words: dataset-executor compareTo timezone UTC-day projection) -- class (a), MEASURED not read: DatasetExecutor's compare math is UTC-calendar throughout (parseUTC reads a bare day as UTC midnight, toISODate emits a UTC day), so a preset window resolved in a non-UTC zone is projected onto UTC day boundaries and the comparison window comes out one day too long, in BOTH directions. Driven through DatasetExecutor.execute, this_month + previousYear at a frozen 2026-09-09: UTC -> ['2025-09-01','2025-09-30'] (30 days, correct); Asia/Shanghai -> ['2025-08-31','2025-09-30'] (31 days, starts a day early); America/New_York -> ['2025-09-01','2025-10-01'] (31 days, ends a day late). NOT a regression -- before this PR the same input was a hard 400 -- and NOT fixed here because a timezone-aware calendar-day extraction in this file is the second implementation the analytics-date-range module header exists to refuse. Carrier: the analytics dateRange lane (#17015 / #17124 / #17596 series).", "noted, not filed: the shared conformance kit's oracle compares full ISO instants, so a face whose real currency is day-granular (this one) can only join the kit by reporting the window the vocabulary resolved and keeping its calendar projection downstream. That split is already the established convention (lowerPreviewDateRange does the same with its bare-day widening) and is documented on both new functions, but the kit's header does not state it, so the next face author has to rediscover it. Carrier: whoever registers a fifth face." ] }Generated by Claude Code
fix(service-analytics): lower a dateRange preset in runCompare instea…