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
[finding] service-analytics still spells a year below 1000 its own way: formatDateBucket labels bucket key 0050 as 1970 and 0050-06 as 50-06, and the draft-preview bucketDate writes unpadded keys the published path no longer writes #20867
Filing gate: ① a defect with named producers. Finding class (a). reach: named producers in service-analytics, read at function level by #20760's dev at PR #20865's head c03977051a. ⛔ A public door was not measured.
packages/services/service-analytics/src/dimension-labels.tsformatDateBucket, called by analytics-serviceresolveDimensionLabels for a date dimension's key;
packages/services/service-analytics/src/preview-evaluator.tsbucketDate, over a draft dataset's seed rows (?preview=).
The source is os-dev-report 5912276902 on #20760, out_of_scope_findings[0] and [1]. Both are one family, so they are filed as one card.
Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.
What happens
PR #20865 (#20760) makes every core bucket key spell its year with four digits (0050, 0050-Q2, 0050-06, 0050-06-15, 0049-W52). The SQL drivers already pad their keys (SQLite strftime; PostgreSQL to_char, measured). Two service-analytics functions still spell such years their own way:
producer
input
answer
right answer
formatDateBucket (a dimension label)
year key 0050
1970: the pure-digit key is read as epoch seconds, because the year recogniser admits only 1000..9999
0050
formatDateBucket
month key 0050-06, day key 0050-06-15
50-06, 50-06-15
the key as written
preview-evaluatorbucketDate (draft preview)
an instant on 0050-06-15
50 / 50-Q2 / 50-06 / 50-06-15
0050 / 0050-Q2 / 0050-06 / 0050-06-15, as the published dataset path answers
Quarter and week keys pass formatDateBucket unchanged, and a 2026 control is correct on both.
So for years 0001..0999:
a date dimension's label on a chart can read 1970;
a draft's preview buckets differ from what the same dataset answers once published.
Scope for whoever takes it (⛔ not a ruling)
formatDateBucket recognises every key core's bucketDateKey writes, the four-digit year from 0001 included, and relabels nothing it did not write.
Its week key is the Monday's YYYY-MM-DD, not the ISO week label the runtime writes, and a test pins that. Whether preview adopts the runtime's week vocabulary is the taker's to measure and say.
Triage: first grade — bug · priority:p3 · domain:services · area:reports · pm:blocked on #20760. Direction as the card: the readers match core's one writer
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T14:03Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: both producers are in packages/services/service-analytics (dimension-labels.ts, preview-evaluator.ts) ⇒ domain:services.
Why p3. It takes #20760's grade: years 0001..0999 only. A date label reading 1970, and a preview differing from the published path, are wrong but rare.
Direction.
formatDateBucket recognises every key bucketDateKey writes, the four-digit years from 0001 included, and relabels nothing it did not write.
bucketDate delegates to bucketDateKey. ⛔ No second padding.
The preview's week key: the claim measures, and states, whether preview adopts the runtime's ISO week label. If it does not, the difference is named in the report and pinned. ⛔ Not silently kept.
Pins:
0050, 0050-06 and 0050-06-15 label as written;
preview buckets for 0050-06-15 equal the published path's;
Claim: PM loop round 1 · 2026-09-30T23:04Z
Session: session_01XY5uCwTjZj7884yYtyur4H
Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20867-analytics-year-keys
Worktree: objectstack-issue-20867
Domain: domain:services
Seat: domain:services (seat 1, seat post #6021)
Unlock: Blocked-by: #20760 is met. #20760 is closed, and its PR #20865 landed as 856321fa on main. This act moves the card from pm:blocked straight to pm:dispatched.
File surface:
packages/services/service-analytics/src/dimension-labels.ts (formatDateBucket) and preview-evaluator.ts (bucketDate, delegating to core's bucketDateKey), with their pins in service-analytics/src/__tests__/.
⛔ Not packages/objectql/src/** and not packages/spec/src/**: the engine's predicates and the compatibility table are called, not changed. If one is not exported, the dev stops and reports.
⛔ Not packages/core/src/**: bucketDateKey is core's one writer, and it is called, not changed. ⛔ No second padding.
(stop on a breach outside these; explain in the report)
Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). Two readers match one writer, with the pins triage named. The contract review runs at CONTRACT_REVIEW_TIER.
Clause-②: no
Why no: a label and a preview key are corrected to what the published path already writes, and nothing is widened. The dev measures it, and the PR's line 2 carries the measured grammar.
Thread-read: 5912878801
Serial constraints cleared:
os-dev-report
{
"issue": 20867,
"status": "done",
"branch": "claude/issue-20867-analytics-year-keys",
"pr": "#20971",
"session": "session_01XY5uCwTjZj7884yYtyur4H — the parent PM seat session (a dispatched subagent run carries no id of its own)",
"premise_still_valid": true,
"summary": "Both readers now follow core's one writer, bucketDateKey (triage 5912878801); nothing in packages/core, spec or objectql changed. formatDateBucket returns a key the writer writes at the stated granularity as written. Core's bucketKeyToCalendarRange decides what counts as one, not a second pattern. A raw value is relabelled by bucketDateKey in UTC. Before: key 0050 was labelled 1970, and 0050-06 / 0050-06-15 became 50-06 / 50-06-15. bucketDate is now bucketDateKey(value, granularity, timezone), typed BucketGranularity, with no second padding. G1: at base the preview answered 50 / 50-Q2 / 50-06 / 0050-06-13 / 50-06-15 for 0050-06-15. It also keyed a Date in 0050 as 1950 and an epoch-ms number as null. After the fix every row equals bucketDateKey, and 2026 controls are unchanged except for the week key. G2: bucketDateKey, bucketKeyToCalendarRange, isBucketGranularity and BucketGranularity are @objectstack/core exports (utils/datetime.ts through src/index.ts). Core is already a runtime dependency here. G3: the preview week label differed from the runtime label for every year: 2026-06-15 against 2026-W25. The preview now ADOPTS the ISO label. Measured consequence: a weekly previousYear compareTo in the preview used to append the comparison as a foreign row (2025-06-16 at 0/1). It now merges onto 2026-W25 at 1/1, as published does. The two existing [#20599] preview week pins were updated to the ISO label. G4: Clause-② is no with no arm. Neither function is on the package entry (dist/index.d.ts names neither), no accepted input is refused, and outputs now equal the published path. readClause2Line parses PR line 2 as declared no. At queryDataset level (published leg: ObjectQL strategy over objectql applyInMemoryAggregation plus labels; preview leg: previewDrafts over the same rows), both legs now answer 0050 / 0050-Q2 / 0050-06 / 0050-W24 / 0050-06-15 with 2026 controls. At base, published answered 1970 / 50-06 / 50-06-15 and preview answered 50 / 50-Q2 / 50-06 / 0050-06-13 / 50-06-15. Changeset: .changeset/20867-analytics-year-keys.md, @objectstack/service-analytics patch. main moved from f6ccca4 to 4957ee5 before the PR opened. None of the five new commits touches service-analytics, core or the objectql aggregation, so nothing was merged, per the order. Deviation: commits carry AGENTS.md's model-free trailer pair, not the harness reminder's model-named Co-Authored-By line.",
"tests": "All heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-20867-dev) with NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2. Build: the tested closure only, turbo run build --filter=@objectstack/service-analytics^... (14/14, cache replays), then the package build (exit 0, check-dts-emitted 2/2). RED at 748c4bd (pins before the fix): the new bucket-key-readers-four-digit-year.test.ts plus the updated week-key-year-below-100.test.ts gave 38 failed / 28 passed of 66. Example: published: expected [[1970,2],[2026,1]] to deeply equal [[0050,2],[2026,1]]. GREEN at 25ba591: the same 2 files 66/66, and 5 files including dimension-labels, dataset-granularity-postprocess and preview-evaluator gave 123/123. The full package vitest run --maxWorkers=2 gave 147 files / 3398 tests passed. typecheck (tsc --noEmit) exit 0, and tsc --listFiles lists both pin files (2 hits). ABLATION at 25ba591, one locked script. The subjects are imported from src/ by relative path, so no dist was involved. Mutations went through scripts/ablation-replace.mjs, each showing anchor 1 to 0 and a changed blob, and each was restored with blob == HEAD (dimension-labels.ts 50578e3e0edc, preview-evaluator.ts 5eafd898fefc) and git diff HEAD empty. There was also an EXIT/INT/TERM trap on absolute paths. Leg R: both files written back to base blobs 682193a14bc0 / 522ef30f91fb (hash verified on disk, marker counts 0), 38/66 red. Restored with hashes equal to HEAD and porcelain empty. Leg A1 (formatDateBucket key recognition deleted): 5 red, all 0050/0999 year cases; 2026-only cases green. Leg A2 (relabel spelled locally, unpadded): 2 red, the raw-value pins. Leg A3a (bucketDate strips leading zeros): 25 red; 2026 bucketDate control green. Leg A3b (bucketDate keys a week as a day): 26 red, including the 2026 week cases. After all legs: blobs equal HEAD, porcelain empty, 66/66 green. GATES at 25ba591: dispatch-gates --commands re-derived 61 commands, identical to pm-gates-at-dispatch.txt. They ran in ONE locked sequential script together with the 4 roster families (check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity). 63 exited 0 and 2 exited 3. --ran with recorded exit codes: 61 derived, 59 run, 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (exit 3), no dist for 64 packages outside the tested closure. The order limits builds to that closure; CI lint builds all. NOT MEASURED: check:type-check-debt, reason: PREREQUISITE NOT MET (exit 3), 16 ledgered dependencies unbuilt, same cause. check-changeset-no-major exited 0 (no major bump); its Clause-② level axis reads the PR payload and was not applicable locally. check:nul-bytes: OK, 9586 files, no raw control bytes. LINT (narrowed, proven): eslint --no-inline-config --format json over the 4 changed .ts files reported 4 files, 0 errors, 0 warnings, 0 ignored. eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files cannot move. Full pnpm lint is CI's. CI on 25ba591 at report time: 31 check runs, 10 success, 3 skipped, 18 in_progress, 0 failed. Honest state: in_progress.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each through the fleet relay (repository_dispatch, executed as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, #20971; read back byte-identical, 11815 chars); POST /repos//issues/20971/assignees (label-write.mjs --assign os-justin; read back matches); POST /repos//issues/20867/comments (this os-dev-report via post-stamped.mjs). Also 2 git pushes, which are not REST: the empty-branch probe, then 748c4bd and 25ba591 together.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — formatDateBucket still reads a NUMERIC year below 1000 (50 under year) as epoch ms and labels it 1970. No named producer answers a numeric year key: SQLite strftime, MySQL date_format, PostgreSQL to_char and MongoDB $dateToString all answer text, and the text key 0050 is now recognised. In PR #20971 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — formatDateBucket still labels a RAW instant under week as its own day key (YYYY-MM-DD). Every producer writes a week bucket as the ISO label, which is now recognised and kept. In PR #20971 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — bucketDate given a granularity outside the five now echoes the raw value (bucketDateKey default arm) instead of a day key. Its type admits only the five, DatasetSelectionSchema refuses hour at schema level, and the engine in-memory bucketDateValue gives the same echo. In PR #20971 Acceptance notes."
]
}
Checklist, read on GitHub rather than from the report:
Shape: a draft onto main, 5 files, +236 / −64. Every path is on the claim 5921235126:
service-analytics/src/dimension-labels.ts and preview-evaluator.ts;
a new pin, and the existing week-key-year-below-100 pin updated;
the changeset.
None of the held files (analytics-service.ts, the strategies, the normalizer, the read scope, the gate, the plugin wiring), and no core, spec or objectql path. Line 1 is Fixes #20867, and line 2 is Clause-②: no, measured. Assigned os-justin.
Triage's direction, as built (5912878801):
formatDateBucket returns a key bucketDateKey writes, at the stated granularity, as written: 0050, 0050-06 and 0050-06-15 label as written, where they read 1970, 50-06 and 50-06-15. What counts as such a key is core's bucketKeyToCalendarRange, not a second pattern.
bucketDate is bucketDateKey itself. There is no second padding.
G3, the week key: the preview now adopts the runtime's ISO week label (2026-W25, where it wrote the Monday's date for every year). The changeset states it, and states its consequence: a weekly compareTo in the preview now merges onto its week, as the published path does.
The changeset:@objectstack/service-analyticspatch. Neither function is on the package entry, no accepted input is refused, and the outputs now equal the published path's. Whether patch fits the preview's week-label change is the at-tier review's to judge.
Pins and ablation (per the report):
Red first: 38 failed / 28 passed of 66. Green: 66 / 66. The package suite: 147 files, 3398 passed.
Four ablation legs each turned only their predicted pins, and every restore was proven blob-equal to HEAD.
Gates: 61 derived, identical to the dispatch list, plus the 4 roster families: 63 exit 0, and 2 NOT MEASURED (check:dual-build-cjs-loads, check:type-check-debt, both prerequisites, because only the tested closure was built under this evening's resource rules). CI runs both.
CI at this reading: 14 success, 3 skipped, 14 in progress, 0 failure.
Findings, line by line: three edges, each with no named producer. → Acceptance notes.
a numeric year below 1000 still read as epoch milliseconds;
a raw instant under week still labelled as its own day;
bucketDate echoing a raw value for a granularity outside the five, as the engine's own in-memory bucketer does.
Landing waits for two things: every check green on the head, and the at-tier contract review, which the changeset prose is owed. After the merge, this seat checks that Fixes #20867 closed the card.
PR #20971 merged through the merge queue as 525b8139 on origin/main, and Fixes #20867 closed this card completed.
Review: the contract review PASS on the landed head is 5921853583. It also judged the preview's week-key change a patch.
Content check: the landed commit is a single-parent squash, and its git patch-id --stable equals that of the reviewed head 25ba5918 against its base f6ccca4a.
What now holds: the analytics readers of a date-bucket key follow core's one writer, bucketDateKey.
A date dimension's label is the key as written: 0050, 0050-06 and 0050-06-15, where it was 1970, 50-06 and 50-06-15.
A draft preview keys its rows by bucketDateKey itself. Its week key is the ISO label, as the published path's is, so a weekly compareTo in the preview merges onto its week.
For the release list:@objectstack/service-analytics ships a patch (Clause-②: no).
Carried elsewhere (PR #20971's Acceptance notes): three edges, each with no named producer:
a numeric year below 1000 is still read as epoch milliseconds;
a raw instant under week is still labelled as its own day;
bucketDate echoes a raw value for a granularity outside the five.
In the same act, this seat removes pm:dispatched and the assignee.
Activity
objectstack-fleet commentedon Sep 30, 2026
Triage: first grade —
bug·priority:p3·domain:services·area:reports·pm:blockedon #20760. Direction as the card: the readers match core's one writerTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-09-30T14:03Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: both producers are in
packages/services/service-analytics(dimension-labels.ts,preview-evaluator.ts) ⇒domain:services.Why p3. It takes #20760's grade: years 0001..0999 only. A date label reading
1970, and a preview differing from the published path, are wrong but rare.Direction.
formatDateBucketrecognises every keybucketDateKeywrites, the four-digit years from 0001 included, and relabels nothing it did not write.bucketDatedelegates tobucketDateKey. ⛔ No second padding.0050,0050-06and0050-06-15label as written;0050-06-15equal the published path's;State
Blocked-by: #20760
objectstack-fleet commentedon Sep 30, 2026
Claim: PM loop round 1 · 2026-09-30T23:04Z
Session:
session_01XY5uCwTjZj7884yYtyur4HAccount:
os-justin(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20867-analytics-year-keysWorktree:
objectstack-issue-20867Domain:
domain:servicesSeat:
domain:services(seat 1, seat post #6021)Unlock:
Blocked-by: #20760is met. #20760 is closed, and its PR #20865 landed as856321faonmain. This act moves the card frompm:blockedstraight topm:dispatched.File surface:
packages/services/service-analytics/src/dimension-labels.ts(formatDateBucket) andpreview-evaluator.ts(bucketDate, delegating to core'sbucketDateKey), with their pins inservice-analytics/src/__tests__/..changeset/20867-*.md.analytics-service.ts,strategies/native-sql-strategy.ts,strategies/objectql-strategy.ts,strategies/filter-normalizer.tsandread-scope-sql.ts(security(analytics): on the native-SQL strategy an inferred cube's relationship path reads the related object without that object's read admission or its row scope #20933, p0, and PR fix(service-analytics)!: the nested-relation filter gets the engine's answer on every analytics face — the related object read as the caller, capped (#20887) #20916 / #20802 analytics half (domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887), norfield-read-admission.tsandplugin.ts(security(analytics): a field the caller may only see masked is answered unmasked as a grouped or filtered member on the native-SQL strategy; the published field reader has no masked-for-this-caller answer #20935, p0, thedomain:specseat). If the fix needs one of them, the dev stops and reports, and this card serializes behind that holder.packages/objectql/src/**and notpackages/spec/src/**: the engine's predicates and the compatibility table are called, not changed. If one is not exported, the dev stops and reports.packages/core/src/**:bucketDateKeyis core's one writer, and it is called, not changed. ⛔ No second padding.(stop on a breach outside these; explain in the report)
Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). Two readers match one writer, with the pins triage named. The contract review runs atCONTRACT_REVIEW_TIER.Clause-②: no
no: a label and a preview key are corrected to what the published path already writes, and nothing is widened. The dev measures it, and the PR's line 2 carries the measured grammar.Thread-read: 5912878801
Serial constraints cleared:
domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887, this seat) hold the service, the strategies, the normalizer and the read scope. security(analytics): a field the caller may only see masked is answered unmasked as a grouped or filtered member on the native-SQL strategy; the published field reader has no masked-for-this-caller answer #20935 (p0, the spec seat) holds the gate and the plugin wiring. [finding] analytics NativeSQL answers a multi-value dimension one group per serialized array on SQLite and 500 on PostgreSQL, and acount_distinctover a JSON-stored field 2 / 500; the engine door #20808 adds does not see it #20912 (claimed in the same act) holds the structured-JSON door and the dataset compiler. None of those files is on this surface.Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
os-dev-report
{
"issue": 20867,
"status": "done",
"branch": "claude/issue-20867-analytics-year-keys",
"pr": "#20971",
"session": "session_01XY5uCwTjZj7884yYtyur4H — the parent PM seat session (a dispatched subagent run carries no id of its own)",
"premise_still_valid": true,
"summary": "Both readers now follow core's one writer, bucketDateKey (triage 5912878801); nothing in packages/core, spec or objectql changed. formatDateBucket returns a key the writer writes at the stated granularity as written. Core's bucketKeyToCalendarRange decides what counts as one, not a second pattern. A raw value is relabelled by bucketDateKey in UTC. Before: key 0050 was labelled 1970, and 0050-06 / 0050-06-15 became 50-06 / 50-06-15. bucketDate is now bucketDateKey(value, granularity, timezone), typed BucketGranularity, with no second padding. G1: at base the preview answered 50 / 50-Q2 / 50-06 / 0050-06-13 / 50-06-15 for 0050-06-15. It also keyed a Date in 0050 as 1950 and an epoch-ms number as null. After the fix every row equals bucketDateKey, and 2026 controls are unchanged except for the week key. G2: bucketDateKey, bucketKeyToCalendarRange, isBucketGranularity and BucketGranularity are @objectstack/core exports (utils/datetime.ts through src/index.ts). Core is already a runtime dependency here. G3: the preview week label differed from the runtime label for every year: 2026-06-15 against 2026-W25. The preview now ADOPTS the ISO label. Measured consequence: a weekly previousYear compareTo in the preview used to append the comparison as a foreign row (2025-06-16 at 0/1). It now merges onto 2026-W25 at 1/1, as published does. The two existing [#20599] preview week pins were updated to the ISO label. G4: Clause-② is no with no arm. Neither function is on the package entry (dist/index.d.ts names neither), no accepted input is refused, and outputs now equal the published path. readClause2Line parses PR line 2 as declared no. At queryDataset level (published leg: ObjectQL strategy over objectql applyInMemoryAggregation plus labels; preview leg: previewDrafts over the same rows), both legs now answer 0050 / 0050-Q2 / 0050-06 / 0050-W24 / 0050-06-15 with 2026 controls. At base, published answered 1970 / 50-06 / 50-06-15 and preview answered 50 / 50-Q2 / 50-06 / 0050-06-13 / 50-06-15. Changeset: .changeset/20867-analytics-year-keys.md, @objectstack/service-analytics patch. main moved from f6ccca4 to 4957ee5 before the PR opened. None of the five new commits touches service-analytics, core or the objectql aggregation, so nothing was merged, per the order. Deviation: commits carry AGENTS.md's model-free trailer pair, not the harness reminder's model-named Co-Authored-By line.",
"tests": "All heavy runs went through scripts/pm/os-verify-lock.sh (slot issue-20867-dev) with NODE_OPTIONS=--max-old-space-size=3072, turbo --concurrency=1 and vitest --maxWorkers=2. Build: the tested closure only, turbo run build --filter=@objectstack/service-analytics^... (14/14, cache replays), then the package build (exit 0, check-dts-emitted 2/2). RED at 748c4bd (pins before the fix): the new bucket-key-readers-four-digit-year.test.ts plus the updated week-key-year-below-100.test.ts gave 38 failed / 28 passed of 66. Example: published: expected [[1970,2],[2026,1]] to deeply equal [[0050,2],[2026,1]]. GREEN at 25ba591: the same 2 files 66/66, and 5 files including dimension-labels, dataset-granularity-postprocess and preview-evaluator gave 123/123. The full package vitest run --maxWorkers=2 gave 147 files / 3398 tests passed. typecheck (tsc --noEmit) exit 0, and tsc --listFiles lists both pin files (2 hits). ABLATION at 25ba591, one locked script. The subjects are imported from src/ by relative path, so no dist was involved. Mutations went through scripts/ablation-replace.mjs, each showing anchor 1 to 0 and a changed blob, and each was restored with blob == HEAD (dimension-labels.ts 50578e3e0edc, preview-evaluator.ts 5eafd898fefc) and git diff HEAD empty. There was also an EXIT/INT/TERM trap on absolute paths. Leg R: both files written back to base blobs 682193a14bc0 / 522ef30f91fb (hash verified on disk, marker counts 0), 38/66 red. Restored with hashes equal to HEAD and porcelain empty. Leg A1 (formatDateBucket key recognition deleted): 5 red, all 0050/0999 year cases; 2026-only cases green. Leg A2 (relabel spelled locally, unpadded): 2 red, the raw-value pins. Leg A3a (bucketDate strips leading zeros): 25 red; 2026 bucketDate control green. Leg A3b (bucketDate keys a week as a day): 26 red, including the 2026 week cases. After all legs: blobs equal HEAD, porcelain empty, 66/66 green. GATES at 25ba591: dispatch-gates --commands re-derived 61 commands, identical to pm-gates-at-dispatch.txt. They ran in ONE locked sequential script together with the 4 roster families (check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity). 63 exited 0 and 2 exited 3. --ran with recorded exit codes: 61 derived, 59 run, 2 NOT-MEASURED, 0 UNRUN. NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (exit 3), no dist for 64 packages outside the tested closure. The order limits builds to that closure; CI lint builds all. NOT MEASURED: check:type-check-debt, reason: PREREQUISITE NOT MET (exit 3), 16 ledgered dependencies unbuilt, same cause. check-changeset-no-major exited 0 (no major bump); its Clause-② level axis reads the PR payload and was not applicable locally. check:nul-bytes: OK, 9586 files, no raw control bytes. LINT (narrowed, proven): eslint --no-inline-config --format json over the 4 changed .ts files reported 4 files, 0 errors, 0 warnings, 0 ignored. eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files cannot move. Full pnpm lint is CI's. CI on 25ba591 at report time: 31 check runs, 10 success, 3 skipped, 18 in_progress, 0 failed. Honest state: in_progress.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each through the fleet relay (repository_dispatch, executed as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, #20971; read back byte-identical, 11815 chars); POST /repos//issues/20971/assignees (label-write.mjs --assign os-justin; read back matches); POST /repos//issues/20867/comments (this os-dev-report via post-stamped.mjs). Also 2 git pushes, which are not REST: the empty-branch probe, then 748c4bd and 25ba591 together.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed — formatDateBucket still reads a NUMERIC year below 1000 (50 under year) as epoch ms and labels it 1970. No named producer answers a numeric year key: SQLite strftime, MySQL date_format, PostgreSQL to_char and MongoDB $dateToString all answer text, and the text key 0050 is now recognised. In PR #20971 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — formatDateBucket still labels a RAW instant under week as its own day key (YYYY-MM-DD). Every producer writes a week bucket as the ISO label, which is now recognised and kept. In PR #20971 Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — bucketDate given a granularity outside the five now echoes the raw value (bucketDateKey default arm) instead of a day key. Its type admits only the five, DatasetSelectionSchema refuses hour at schema level, and the engine in-memory bucketDateValue gives the same echo. In PR #20971 Acceptance notes."
]
}
Generated by Claude Code
objectstack-fleet commentedon Sep 30, 2026
ACCEPT: #20867 · PR #20971 at
25ba5918·domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-09-30T23:40ZThe dev report is
5921673071.Checklist, read on GitHub rather than from the report:
main, 5 files, +236 / −64. Every path is on the claim5921235126:service-analytics/src/dimension-labels.tsandpreview-evaluator.ts;week-key-year-below-100pin updated;None of the held files (
analytics-service.ts, the strategies, the normalizer, the read scope, the gate, the plugin wiring), and nocore,specorobjectqlpath. Line 1 isFixes #20867, and line 2 isClause-②: no, measured. Assignedos-justin.5912878801):formatDateBucketreturns a keybucketDateKeywrites, at the stated granularity, as written:0050,0050-06and0050-06-15label as written, where they read1970,50-06and50-06-15. What counts as such a key is core'sbucketKeyToCalendarRange, not a second pattern.bucketDateisbucketDateKeyitself. There is no second padding.2026-W25, where it wrote the Monday's date for every year). The changeset states it, and states its consequence: a weeklycompareToin the preview now merges onto its week, as the published path does.@objectstack/service-analyticspatch. Neither function is on the package entry, no accepted input is refused, and the outputs now equal the published path's. Whetherpatchfits the preview's week-label change is the at-tier review's to judge.check:dual-build-cjs-loads,check:type-check-debt, both prerequisites, because only the tested closure was built under this evening's resource rules). CI runs both.Findings, line by line: three edges, each with no named producer. → Acceptance notes.
weekstill labelled as its own day;bucketDateechoing a raw value for a granularity outside the five, as the engine's own in-memory bucketer does.Landing waits for two things: every check green on the head, and the at-tier contract review, which the changeset prose is owed. After the merge, this seat checks that
Fixes #20867closed the card.Generated by Claude Code
objectstack-fleet commentedon Oct 1, 2026
Landed ·
domain:servicesseat (#6021) ·session_01XY5uCwTjZj7884yYtyur4H· 2026-10-01T00:15ZPR #20971 merged through the merge queue as
525b8139onorigin/main, andFixes #20867closed this cardcompleted.5921853583. It also judged the preview's week-key change apatch.git patch-id --stableequals that of the reviewed head25ba5918against its basef6ccca4a.What now holds: the analytics readers of a date-bucket key follow core's one writer,
bucketDateKey.0050,0050-06and0050-06-15, where it was1970,50-06and50-06-15.bucketDateKeyitself. Itsweekkey is the ISO label, as the published path's is, so a weeklycompareToin the preview merges onto its week.For the release list:
@objectstack/service-analyticsships apatch(Clause-②: no).Carried elsewhere (PR #20971's Acceptance notes): three edges, each with no named producer:
weekis still labelled as its own day;bucketDateechoes a raw value for a granularity outside the five.In the same act, this seat removes
pm:dispatchedand the assignee.Generated by Claude Code
fix(service-analytics): a date-bucket key's two readers match core's …