Skip to content

refactor(service-analytics): set the drill sidecars on the declared AnalyticsResult - #20762

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20727-retire-drill-augmentation
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20727-retire-drill-augmentation

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #20727
Clause-②: no

AnalyticsResult (@objectstack/spec/contracts) has declared dimensionFields, drillRawRows, drillRawTotals and drillRanges since #20700 (PR #20720). This retires the service's module-private AnalyticsResultWithDrill augmentation and sets the four members on the declared type, with no cast. No answer changes, and nothing published changes.

What changed

  • packages/services/service-analytics/src/analytics-service.ts: deleted type AnalyticsResultWithDrill (at :100 on the base) and its doc comments. The four sets in answerDataset now write result.dimensionFields, result.drillRawRows, result.drillRawTotals and result.drillRanges on result: AnalyticsResult. Each was (result as AnalyticsResultWithDrill).… (:1970, :1973, :1985, :2034 on the base). No new local type, no other cast, no contract change.
  • Two of the deleted doc comments disagreed with the emission: the drillRanges bounds and omission rule, and dimensionFields values, which can be relationship paths. The contract's descriptions follow the emission and are now the only text.
  • New pin: src/__tests__/drill-sidecars-emission.test.ts (1 test).
  • Not touched: the previewDrafts early return (triage ruled it out of this card), and packages/spec.

Premises measured (dispatch D1 to D4)

  • D1: the sets type-check on the declared type. pnpm --filter @objectstack/service-analytics run typecheck exits 0 with the casts gone. tsc --listFiles holds 138 of 138 src/__tests__ files, the new pin included. Two reverse checks ran through scripts/ablation-replace.mjs, each with the restore proven (blob equals HEAD, git diff HEAD empty):
    • A wrong value shape at the dimensionFields set gives TS2322 at (1923,7): the type { [k: string]: number; } is not assignable to the declared Record of string to string. (The message's angle-bracket spelling is written out here in words.)
    • An undeclared member name at the drillRanges set gives TS2551: Property 'drillRangez' does not exist on type 'AnalyticsResult'. Did you mean 'drillRanges'? at (1987,14).
    • So each set is checked against the member AnalyticsResult declares. The emission and the contract agree.
  • D2: every reference. git grep AnalyticsResultWithDrill over the whole tracked tree at the base finds 5 hits, all in analytics-service.ts: the type and the four casts. After the edit it finds 0 (exit 1). After a full build, none of the 68 packages/**/dist directories names it, against a positive control (drillRawTotals hits the spec and service-analytics dists).
  • D3: what publishes. Nothing, so skip-changeset. @objectstack/service-analytics was built at the base and after the edit. A determinism control (the base rebuilt unchanged) gave the same six hashes.
    • index.js, index.cjs, index.d.ts, index.d.cts: byte-identical (cmp exit 0 for each).
    • index.js.map, index.cjs.map: differ in position data only. Decoded, version, sources and names are identical, and neither map embeds source text. Every segment matches once the original lines of analytics-service.ts after the deleted block move up by 47, except on the four set lines, whose columns moved with the removed cast. A comment-only edit makes the same kind of delta.
    • The retired name has 0 hits in the built dist, before and after. The positive control drillRawTotals hits index.js and index.cjs.
  • D4: the byte-identity proof. No existing fixture emits all four sidecars at once: the two totals fixtures in query-dataset.test.ts group by no date dimension. So the pin's fixture joins two existing ones: the matrix "X by time" dataset (stage beside a month-bucketed close_date), and the per-grouping driver of the subtotal test, with totals: { groupings: [['stage'], []] }.
    • Its full answer (8 keys, 744 bytes) was serialised at the base and after the edit. The sha256 was 06130c86…3ec980 both times, and cmp exits 0. The same bytes came back on the final head.
    • The pin reads the answer as AnalyticsResult with no cast, and holds the four keys, their order and their serialised bytes.

Pin and ablation

  • The pin was committed first (285a39f6e) and was green on the unedited source. The retirement follows in 725819485.
  • Ablation on the committed state, in WRAP mode inside an outer restore trap: result.drillRawTotals = … becomes void … (anchor 1 to 0, blob d22f648e to 9be86803).
    • The pin goes red: expected [ 'dimensionFields', …(2) ] to deeply equal [ 'dimensionFields', …(3) ].
    • Restore: the blob equals HEAD (d22f648e) and git diff HEAD is empty.
    • The subject is imported relatively, so it resolves to src/ and no dist leg applies.

Verification, at 39d49081b

This is the head after merging origin/main at a6866da0c, which touched two other service-analytics files.

  • pnpm --filter @objectstack/service-analytics run typecheck: exit 0.
  • pnpm --filter @objectstack/service-analytics test: 141 files and 3267 tests passed.
  • The contract pins from spec(contracts): the dataset answer's drill sidecars (dimensionFields, drillRawRows, drillRawTotals, drillRanges) are emitted by service-analytics and read by objectui, but AnalyticsResult declares none of them #20700, packages/spec src/api/analytics.test.ts and src/contracts/analytics-service.test.ts: 2 files and 40 tests passed.
  • Gates.
    • dispatch-gates --commands derived 55. All 55 ran with exit 0, and --ran reads "55 derived, 55 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero with every line carrying its exit code.
    • Also run, all exit 0: the dispatch's 7 changeset-keyed families, and the four roster families outside the list (check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity).
    • check:dual-build-cjs-loads and check:type-check-debt first answered exit 3 (unbuilt packages). Both were green after a full build.
  • Lint, a declared narrowing. The repo-wide pnpm lint is CI's to run.
    • Population: eslint's config puts both changed .ts files in scope (isPathIgnored is false).
    • Count: eslint --no-inline-config --format json reads 2 files, 0 errors, 0 warnings.
    • Invariance: the config enables no type-aware linting (eslint.config.mjs:327-328, and calculateConfigForFile shows no parserOptions.project or projectService for either file). It reads only baseline JSONs this diff does not touch, so no untouched file's verdict can move.

Acceptance notes

  • No out-of-scope finding to file.
  • carrier: none · noted, not filed. The previewDrafts question the card carried is answered by triage for this card: a preview answer carries no sidecars, and the early return and the contract sentence stay. A consumer that needs preview drill is a card of its own.
  • The D3 reading, for the seat: the published .map files move by position only (above). By the package's policy that is the same class as a comment edit, so this PR carries skip-changeset and no changeset. If the seat reads a position-only map delta as published, a patch changeset on @objectstack/service-analytics is the whole remedy.

Generated by Claude Code

…yte for byte

One dataset answer carries all four drill-through sidecars at once: a
stage grouping beside a month-bucketed date dimension, with a per-stage
subtotal and the grand total. The answer is read as the declared
AnalyticsResult with no cast, and the serialised sidecars are pinned
byte for byte, so the retirement of the local augmentation that follows
can be shown to move nothing.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
…nalyticsResult

AnalyticsResult declares dimensionFields, drillRawRows, drillRawTotals
and drillRanges with the shapes the service emits. The module-private
AnalyticsResultWithDrill augmentation that shadowed them is deleted,
and the four sets in answerDataset write the declared members with no
cast. Two of the deleted doc comments disagreed with the emission
(drillRanges bounds and omission rule; dimensionFields values can be
relationship paths); the contract's own descriptions follow the
emission and now stand alone.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 30, 2026
@github-actions github-actions Bot added the tests label Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 6 documentable anchor(s).

⛔ 1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via drillRanges (symbol, a field of type AnalyticsResultWithDrill), drillRawTotals (symbol, a field of type AnalyticsResultWithDrill))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 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 — 10 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 a51920f5fb1059ae6e8c7b1a96aa785f1da5d248 → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 a51920f5fb1059ae6e8c7b1a96aa785f1da5d248 → 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: 39d49081b094815b5048a4535eee5ed637fc3c74
Local-runs: none

PR #20762 for card #20727. The head is the merge of the refactor commit 72581948563071a709b815a6b1a0524f64c89b7e with origin/main at a6866da0c7d52fcd3ef5f6195db300c5bdf0767c. Read against the card body, triage's direction 5901408894, the seat's serial-wait note and claim, the dev report 5903233736, the seat's ACCEPT 5903281582, and the contract declaration #20700 / PR #20720 (landed 671d4c164f). Inputs: the PR body, its file list, the net diff of the head against its merge base read from an owned ref (refs/review/pr-20762) in the shared checkout, and the head's check-runs. Nothing was built, run or re-run; this record's own keyed lines were checked offline through the readers record-recognisers.mjs exports and post-stamped.mjs --dry-run, which is the tool's own check of a record.

① Derived judgments

  • Net diff against main: 2 files, +105 / −51. packages/services/service-analytics/src/analytics-service.ts (−51 / +4) and the new pin packages/services/service-analytics/src/__tests__/drill-sidecars-emission.test.ts (+101). The diff against the merge base equals the PR's file list; the merge commit contributes nothing of its own.
  • Public surface of @objectstack/service-analytics: unchanged — right. AnalyticsResultWithDrill was a module-private type with no export. git grep at the merge base finds its 5 occurrences, all in analytics-service.ts (the alias at :100, the four casts at :1970, :1973, :1985, :2034), and 0 occurrences anywhere in the tree at the head. No export, no .d.ts member and no subpath ever named it, so nothing published in types or exports can move.
  • Types: neither narrowed nor widened — right. The four sets now type-check against the members AnalyticsResult declares (packages/spec/src/contracts/analytics-service.ts:170, :181, :192, :212 on main, from PR feat(spec): AnalyticsResult declares the four drill-through sidecars #20720), whose TS types are the deleted augmentation's, character for character: a string-to-string record; an array of string-keyed records aligned to rows; an array of arrays of string-keyed records aligned to totals[i].rows; an array of records of { field, gte, lt } strings. packages/spec is untouched, so triage's "no widening of the contract" holds by construction, and the dev's two reverse checks (a wrong value shape at the dimensionFields set is TS2322; an undeclared member name at the drillRanges set is TS2551) show each set is judged against the declared member and not against a local shadow.
  • Emitted answers: unchanged — right. A type alias and four type assertions erase at build; the four assignments are the same four statements on the same object in the same order. The pin was committed first (285a39f6e0ac13010acaad5e8c0bab16248dd5f4, green on the unedited source), the refactor second, so its green on both sides is the before/after proof triage asked for. It reads the answer as AnalyticsResult with no cast, and it holds the four keys, their order and their serialised bytes on the one fixture shape that emits all four at once (stage beside a month-bucketed close_date, with a per-stage subtotal and the grand total). The pinned bytes agree with the contract's descriptions: dimensionFields lists the equality dimension only, drillRanges carries YYYY-MM-DD half-open bounds for a type: 'date' source, and the grand-total grouping contributes an empty map per row in drillRawTotals. The fixture and its driver follow the spellings the package's own query-dataset.test.ts already uses.
  • Accept set: untouched — right. No schema, no request parsing and no refusal moves; the previewDrafts early return lies outside every hunk, exactly as triage ruled for this card.
  • ADR anchoring kept — right. The deleted docblock named ADR-0021 D2; the id still stands at the drill block itself (head :1911, the comment ADR-0021 D2 — drill-through metadata) and in 16 further places in the file, and no scripts/adr-anchors/ entry names this file, so nothing the anchor gate reads moved.
  • The two stale comments the card named (the drillRanges bounds and omission rule; dimensionFields values that can be relationship paths) go with the block. The contract's descriptions, which follow the emission, are now the only text. Right.
  • Docs drift bot on the PR lists content/docs/releases/v16.mdx, which names drillRanges and drillRawTotals as fields of AnalyticsResultWithDrill. That page is RELEASE-OWNED and read-only for a code PR (AGENTS.md, Documentation Guardrails); it describes v16 as it shipped and is not made wrong by retiring the alias now. Leaving it is right.

② Semver level

  • Reading: skip-changeset is right; no patch changeset is owed.
  • Governing text, in order.
    • AGENTS.md, Post-Task Checklist step 3: "A bug fix in a released package takes a patch changeset — never none, and ⛔ never skip-changeset: that label is for a diff that publishes nothing from any released package." This diff is not a bug fix — no answer, type or export moves — so the first sentence does not bind. The question is the second sentence: does the diff publish anything from a released package?
    • The fleet's operational reading of that sentence, .claude/agents/os-dev.md:298-300: 「发布面动了要 changeset;skip-changeset 判据是没动:已发布 = 各包 files[] 实际发运内容。」 「快速通道:docs/adr/** · .claude/** · scripts/pm/** · 仓根配置 · 私有包 · 注释,不发布。」 「其余实测:构建后 grep files[] 所列路径找符号,带正控;符号零命中、正控命中 ⇒ 不发布。」 Published means what each package's files[] ships; comments are fast-laned as not published; everything else is measured by building and grepping the shipped paths for the symbol beside a positive control.
    • Triage 5901408894: "A patch on @objectstack/service-analytics, or none if the package's policy treats a type-only internal change as unreleased. The dev states which." There is no per-package policy; the policy that applies is the text above, and the dev stated none, with the measurement below.
  • Applied. @objectstack/service-analytics is a released package (publishConfig.access: public, version 17.5.0, files: ["dist", "README.md", "CHANGELOG.md"], a member of the changeset fixed group). The dev's D3 is the os-dev.md test verbatim: built at the base and after the edit with a determinism control, index.js, index.cjs, index.d.ts and index.d.cts are byte-identical (cmp exit 0 each); the retired symbol has 0 hits in all six shipped dist files before and after; the positive control drillRawTotals hits index.js and index.cjs. Under 「符号零命中、正控命中 ⇒ 不发布」 that is "not published". This record does not re-run that build (read-only shape); the result is what the diff must produce, since a type alias and four type assertions erase to nothing, and the seat's ACCEPT read the same figures.
  • The one contested residue. The two .map files ship under files: ["dist"] (the root tsup.config.ts sets sourcemap: true; dropSourcesContent strips the source text), and they differ by mapping positions only — version, sources and names identical — because the deleted block shifts every later line up by 47 and the four set lines lose the cast's columns. That is not "publishing something", on the repository's own readings: (a) a comment-only edit in the same source produces exactly this delta class, and os-dev.md fast-lanes comments as 「不发布」, so the policy has already classified it; (b) scripts/ablation-dist-preflight.mjs:91-93 reads a sourcemap-only difference as proof that a rebuild happened, not that the running code changed — sourcemaps do not execute; (c) with no sourcesContent, the shipped maps disclose no changed source byte to any consumer, only a renumbered mapping into a file the tarball does not contain. A patch here would publish a CHANGELOG sentence describing a change no consumer can observe, which is the noise the changeset rule keeps out of the grep an upgrading agent performs.
  • So the label stands and no changeset is owed. The Check Changeset context's latest run is skipped by that label; its first run at the opened event concluded success on the settling read, so no gate has ever been red on this head over it.
  • The PR body's Clause-② declaration, no with no arm: right. The line answers whether this card widens the accept set or enlarges the public surface, and both halves are no: the accept set is untouched, the four members were published on AnalyticsResult by PR feat(spec): AnalyticsResult declares the four drill-through sidecars #20720 (a minor, declared widening, paid there), and this diff removes only a private alias. Nothing narrows, so no arm belongs, and no (widening) would be the malformed contradiction the reader refuses. The line sits bare on the body's second line, on a line of its own, the shape readClause2Line grades declared.

③ Boundary flags

  • Dev flag, D3 (skip-changeset against a position-only .map delta): answered in ② — the label stands.
  • Dev flag, D4 premise corrected (no existing fixture emits all four sidecars; the pin composes one from the matrix X-by-time dataset and the subtotal test's per-grouping driver): accepted. Triage asked for a byte-identical answer on one drill fixture, and one fixture carrying all four is the stronger reading of that sentence. The committed pin holds the four sidecars' bytes and order; the full-answer sha256 held across base, edit and merge head is the dev's reported measurement and is consistent with an erasure-only diff.
  • Dev deviation, merge of origin/main before the PR opened: accepted — AGENTS.md Multi-agent discipline §10 asks for it; the net contribution over the merge base is the same two files, and the report states the gate union was re-run on the merged head.
  • Dev deviation, the model-free trailer pair: the three commits carry the session-URL trailer and a plain Co-authored-by: Claude line, and no model identifier — the AGENTS.md form. Accepted.
  • open_questions: [], out_of_scope_findings: []: nothing to escalate, and this record adds no finding.
  • Card open question, previewDrafts drill sidecars: triage answered "no, not in this card"; the diff leaves the preview early return and the contract sentence untouched, and the PR's acceptance note records it as noted, not filed. Closed for this card; a consumer that needs preview drill is a card of its own, as triage wrote.
  • Seat's provisional acceptance of D3: resolved above.
  • Check-runs on 39d49081b094815b5048a4535eee5ed637fc3c74, latest run per check name, read at 2026-09-30T03:16Z: of the seven required contexts, Lint & Repo Gates, TypeScript Type Check, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL) and Governed Surface Queue Guard are success; Test Core is still in progress as the aggregate (all six shards Test Core (1/6) to (6/6) are success). Advisory: Check Changeset skipped by the label, Check PR Size and Auto Label skipped on the label event after earlier success runs, Console Pin Gate, Build Docs and Packed-tarball smoke (opt-in) skipped by filter, and every other run success. Nothing is red. Landing still waits for the Test Core aggregate to conclude success; this record's verdict is on the diff and does not stand in for that conclusion.

Implemented-by: claude/issue-20727-retire-drill-augmentation
Reviewed-by: session_01XY5uCwTjZj7884yYtyur4H

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

service-analytics: retire the local AnalyticsResultWithDrill augmentation now that AnalyticsResult declares the four drill sidecars (#20700 landed)

2 participants