Skip to content

feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) - #16750

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16685-boolean-aggregate-members
Sep 8, 2026
Merged

zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16685-boolean-aggregate-members

Conversation

@claude

@claude claude Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #16685

What changed

AGGREGATE_FIELD_TYPE_COMPATIBILITY (packages/spec/src/data/aggregate-field-type-compatibility.ts) accepts the boolean class — boolean, toggle — on its four arithmetic / order rows: sum, avg, min, max. Nothing else in the table moves; count / count_distinct were already total over FieldType.

Executing the director-seat ruling of decision batch #80 (2026-09-08, comment 5577120138 on the card, maintainer verbatim 「其他同意」, option A): batch #59's "every other pair: refused" was a blanket default that never named booleans; ruling #11152 (2026-08-28) — booleans aggregate as NUMBERS on every backend, no per-aggregate exception — is the specific ruling and stands. The spec's own AGGREGATION_CASES pins sum(flag)=3, avg(flag)=0.5, min(flag)=0, max(flag)=1 on six backends and driver-sql casts the aggregand on Postgres (#11635) to make that hold. AGGREGATION_CASES and the driver cast are untouched (「nothing else moves」). Option B is not taken.

Diff, four files:

  • packages/spec/src/data/aggregate-field-type-compatibility.ts — a BOOLEAN_AGGREGATE_FIELD_TYPES constant in the file's existing spelled-out-class idiom, spread into the four rows. The module TSDoc argued the opposite of the ruling in two places (the "everything else" bullet listed booleans among the refused; a dedicated passage recorded the two-ruling collision as unresolved). Both are rewritten to the ruling: the rule table names the boolean class on each row, a boolean bullet cites [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 as the authority and batch Add modern field types (slider, qrcode, geolocation) with configuration schemas #80 as the ruling that resolved the collision, and the "overrides" passage now records only the one override that remains (the string classes on min / max).
  • packages/spec/src/data/aggregate-field-type-compatibility.test.ts — the literal row pins gain the boolean members; the numeric / temporal bucket pins exclude the boolean class before comparing to NUMERIC_VALUE_TYPES / the temporal classes; a new pin holds the boolean bucket equal to BOOLEAN_VALUE_TYPES and present on all four rows; the boolean refusal pins become accept pins; and a cross-pin against AGGREGATION_CASES asserts that every boolean case the conformance suite requires a backend to answer is a pair this table accepts — the two spec tables can no longer contradict each other.
  • .changeset/aggregate-boolean-members.md — new, @objectstack/spec: minor (reasoning below).
  • .changeset/aggregate-field-type-compatibility.md — the table's own PENDING changeset (still in .changeset/, so not yet in any CHANGELOG) said "Booleans are refused … the row ships exactly as batch Validation Protocol: Cross-Field, Async, and Conditional validation #59 stated it" and listed sum × boolean among the refused examples. Both would have published a falsified statement in the same release this lands in, which is the argument the card itself makes about the TSDoc. Corrected in place: the boolean sentences now point at [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 / batch Add modern field types (slider, qrcode, geolocation) with configuration schemas #80; two example pairs in the narrowing sentence are replaced by pairs that remain genuine refusals (sum × boolean → sum × text, min × text → min × json); the string-classes sentence is kept verbatim while the paragraph's lead moves from "Two refused rows…" to "One refused row…". Declared here as a deviation from the card's two-file surface; the edit is prose only.

Changeset level — minor, judged from the diff

No export is added, removed or renamed: pnpm --filter @objectstack/spec check:generated reports all 15 artifacts up to date at 499d718 (api-surface/, export-origins/, declaration-map/, authorable-surface/, docs references all unchanged) — the exported declarations are unchanged (the table's declared type is the same; only its value gains members) and the new BOOLEAN_AGGREGATE_FIELD_TYPES constant is module-private — absent from dist/*.d.ts and from the bundles' export lists (it does ship inside the bundles as a non-exported binding, measured at review). The shipped dist/*.d.ts is NOT byte-identical: it carries the rewritten module TSDoc (a contract-review measurement at 499d718; the first version of this section claimed byte-identity on the strength of check:generated, which compares export listings and signatures, not .d.ts bytes — retracted). The accept set only widens (every pair accepted before is still accepted), and widening a published accept set is the same class of change the table's introduction was (decision batch #35 puts additive widening at minor). It rides the same release as the parent changeset, so the version outcome is identical either way; minor is the honest classification. Not breaking, so no ADR-0087 marker is required (check-adr-0087-registration --base origin/main exit 0).

Clause-② — yes

Adding members widens a published accept set; the card's claim declares Clause-②: yes and this PR carries needs:contract-review on both carriers. check-widening-tells would not fire (declaration is yes), and the widening is exactly the ruled one: two members, four rows.

Verification

Three commits on the branch: 499d718 (the implementation), 299ed0b (contract-review patch round 1: a changeset paragraph, a test header comment, one assertion line), bee6b0b (patch round 2: one changeset sentence). The full derivation and suite below ran at 499d718; the two patch rounds ran the families that read the paths they touched, recorded here so the narrowing is visible on the PR and not only in a report.

Gate narrowing on the patch rounds — declared. dispatch-gates.mjs --commands derives 75 families for the branch's whole change set and has no per-path narrowing flag, so the patch-round subsets were hand-selected. At 299ed0b, 22 commands, all exit 0: check-adr-0087-registration.mjs, check-changeset-no-major.mjs, check-empty-changeset.mjs (each --base origin/main and --self-test), check:changeset-gate-self-tests, check:objectui-changeset, check-closing-keyword-parity.mjs (+ --self-test), check-comment-mask-adoption.mjs (+ --self-test), check-comment-mask-corpus.mjs, check-keyed-text-bounds.mjs (+ --self-test), check:doc-authoring, check:nul-bytes, and the test-file families check:cross-package-test-inputs, check:test-source-alias, check:where-matcher, check:objectql-double-limit, check:type-source-resolution; plus the pin file (Tests 21 passed (21)) and pnpm --filter @objectstack/spec typecheck (exit 0). At bee6b0b, the 17 of those that read .changeset/** or repo-wide text (the same list minus the five test-file families), all exit 0. The other 53 derived families are keyed on the packages/spec/src/** export / docs / liveness / authorable surfaces, on built output (check:dts-closure, check:dual-build-cjs-loads, check:sourcemap-no-sources-content), or on packages/** source scans — a .changeset/*.md edit reaches none of them, and the packages/** scans that do read a test file ran in CI: at 299ed0b all six required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL)) are success. At 499d718 all 75 ran: 73 exit 0, 2 NOT MEASURED (prerequisite not met, CI-owned) — itemised below.

  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0 (under os-verify-lock.sh).
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/aggregate-field-type-compatibility.test.ts — Test Files 1 passed (1), Tests 21 passed (21).
  • pnpm --filter @objectstack/spec test (the local vitest project, the package's own suite) — Test Files 465 passed (465), Tests 12955 passed (12955), 411s, VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck all green; the test file is inside tsconfig.test.json's program).
  • pnpm --filter @objectstack/spec check:generated — All 15 generated artifacts are up to date (exit 0); nothing regenerated, nothing to commit.
  • Gate derivation: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths — the change set is taken from the merge base, 4 paths) printed 75 commands; every one was run with the printed spelling recorded byte-for-byte; --ran reconciles 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). 73 exit 0. Two are NOT MEASURED, both exit 3 PREREQUISITE NOT MET and both needing the whole 87-package closure built, which is CI's run (Build Core, lint.yml): pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt (--re-measure). Two others were cleared after their prerequisite was met: check-plugin-teardown-shape.mjs --self-test (exit 1 on the shallow clone, exit 0 after fetching its pinned fixture commit 621a487, 47 cases) and @objectstack/lint check:doc-formula-expressions (exit 3 before, exit 0 after building formula + lint).
  • Consumer radius: git grep for the table and predicate over packages/** finds only this module, its test and the data/index.ts barrel — the two consumer legs (No layer refuses an incoherent aggregate / field-type pair — a dataset measure avg over a datetime works on SQLite and errors on Postgres #16099, the devx lint leg) are not landed, so no downstream test is owed; the public surface listings (api-surface/, export-origins/) are unchanged (see the changeset section), so no import-side re-test is owed either.

Reverse verification — the pins fail when the boolean class is emptied

Committed first, then mutated. Subject resolved through the relative ./aggregate-field-type-compatibility import (source, not dist/), so no rebuild is part of the loop. Mutation: the BOOLEAN_AGGREGATE_FIELD_TYPES literal replaced with an empty array carrying an ABLATION-16685 marker. On-disk proof by anchored counts: removed text 1 → 0, marker 0 → 1, blob 92a0a4c1… → 98e329ae…. Run: Tests 6 failed | 15 passed (21) — the six failing are exactly the boolean pins (sum row, avg row, min/max row, the boolean-bucket pin, the four-aggregate accept pin, and the AGGREGATION_CASES cross-pin); the totality pins stay green as they should. Expected direction: red; observed: red. Restore: git checkout HEAD -- ABSOLUTE_PATH spelled with HEAD under a trap … EXIT INT TERM; proof git diff HEAD --stat empty, git status --porcelain empty, git hash-object of the restored file = 92a0a4c1f7aae1ec61f4b9064c90985b7a102651 = the HEAD blob, marker count 0.

验收备注

  • noted, not filed: the card cites the driver-sql boolean cast at sql-driver.ts:8497-8516; on origin/main 5e53d73 it sits at :8670-8712. Line drift only, content exactly as the card describes. 承接者:无。
  • noted, not filed: the PM's mechanism assumption that generated artifacts (api-surface, export-origins, authorable-surface, docs references) would need regeneration was measured false — no export or .describe() changed; check:generated is green with nothing to regenerate.
  • Adjacent open questions the card explicitly keeps separate and this PR does not touch: whether progress stays in sum, and count_distinct × percent (isIncoherentAggregate flags it, the table accepts it — pinned as a visible divergence). No layer refuses an incoherent aggregate / field-type pair — a dataset measure avg over a datetime works on SQLite and errors on Postgres #16099 remains open; the devx lint leg remains open.
  • Fence honoured: packages/spec/src/data/analytics.zod.ts, runtime/src/domains/analytics.ts and packages/spec/src/data/seed.zod.ts are untouched.

🤖 Generated with Claude Code


Generated by Claude Code

…aggregate × field-type table (#16685)

Decision batch #80 (2026-09-08) holds ruling #11152 - booleans aggregate as
numbers on every backend, no per-aggregate exception - over batch #59's
blanket "every other pair refused", which never named booleans. The four
arithmetic / order rows gain the BOOLEAN_VALUE_TYPES members; the module
TSDoc and the table's pending changeset no longer publish the refusal; the
pins hold the boolean rows both literally and against AGGREGATION_CASES.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • 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.

Coarse fallback — 130 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 cf33adbd8ea74306f60e1f0b9c827787aeaa383d → packageMentionDocs.

Which tree this was computed on

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

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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Contributor

Contract review (CONTRACT_REVIEW_TIER, isolated seat) — PR #16750 @ 499d718

Verdict: PASS WITH FINDINGS (nothing below blocks; F5 is a CI wait, not a defect)
Ruling implemented: yes — option A of #16685 comment 5577120138, exactly and only.

Reviewed against the ruling verbatim: "The compatibility table adds the boolean and toggle members to sum / avg / min / max. Nothing else moves." Read from the PR head fetched into a throwaway ref and from origin/main; nothing in this review is taken from the PR body's own account.

1. Did exactly the ruled pairs move? — yes, measured cell-by-cell

I rebuilt both tables (merge base 5e53d73 → head 499d718) from their source constants and diffed every AggregationFunction × FieldType cell: 294 cells, 8 changed, all false → true:

aggregate boolean toggle
sum refused → accepted refused → accepted
avg refused → accepted refused → accepted
min refused → accepted refused → accepted
max refused → accepted refused → accepted

count / count_distinct stay at 49/49 (ANY_FIELD_TYPE, untouched). Row sizes: sum 6→8, avg 7→9, min 10→12, max 10→12. The mechanism is one new constant BOOLEAN_AGGREGATE_FIELD_TYPES = ['boolean','toggle'] (packages/spec/src/data/aggregate-field-type-compatibility.ts:142-144) spread into the four rows (:161-168); NUMERIC_ / ADDITIVE_ / TEMPORAL_ are byte-identical to base. AGGREGATION_CASES, driver-sql, analytics.zod.ts, seed.zod.ts are not in the diff. None of the five relevant files moved on main between the merge base and current main (8ccf7a1), so the base reading is current. "Nothing else moves" holds.

2. Does the table agree with what ruling #11152's suite actually asserts? — yes, with one attribution caveat (F1)

Read packages/spec/src/data/aggregation-conformance.ts on main, not the PR body. The flag column (AGGREGATION_ROWS, 3 true / 3 false, :230-235) is exercised by seven cases at :358-430: sum(flag)=3, avg(flag)=0.5, min(flag)=0, max(flag)=1, count(flag)=6, count_distinct(flag)=2, and min(flag) grouped by region (east=1, west=0). Every one of those pairs is accepted by the head table. The six enrolled faces listed at :87-118 each iterate for (const c of AGGREGATION_CASES) with no flag filter (verified in all six harness files; driver-sql's runs on SQLite + PG + MySQL cells when provisioned, no case dialect-gated). The ruled driver-sql Postgres cast is at sql-driver.ts:8670-8691 on current main (the card's :8497 is line drift only).

3. Consumer legs — not landed, not touched, not owed

git grep at the PR head for AGGREGATE_FIELD_TYPE_COMPATIBILITY / isAggregateCompatibleWithFieldType outside the module and its test finds only the data/index.ts barrel and the generated api-surface / export-origins entries. No hit under packages/lint/**, services/**, packages/objectql/**, runtime/**. #16099 is open and pm:blocked; the devx lint leg has no file yet. So the PR lands the widened table before either consumer executes it — which is the whole point of the ruling's time box. Nothing downstream to keep green.

4. Changeset and governed paths

  • .changeset/aggregate-boolean-members.md: "@objectstack/spec": minor — package name matches packages/spec/package.json; the body states the before (refused via batch Validation Protocol: Cross-Field, Async, and Conditional validation #59's default) and after (accepted, with the row table) explicitly. Not breaking (accept set only widens; dist/*.d.ts type unchanged), so no ADR-0087 marker owed; Check Changeset green. minor is correct.
  • .changeset/aggregate-field-type-compatibility.md: edited outside the claim's two-file surface. Verified it is still pending (0 hits for the export name in packages/spec/CHANGELOG.md on main), so this corrects unreleased text rather than rewriting history; the diff is two prose lines and the string-class override paragraph is verbatim. Declared in the PR body. Acceptable.
  • Governed paths: none touched. No docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/** in the diff. Governed Surface Queue Guard green. This is not a maintainer-only merge on governed grounds.

5. Tests — what the file actually pins (re-read, not trusted)

packages/spec/src/data/aggregate-field-type-compatibility.test.ts at head. If the boolean members are removed again (whole class, or either spelling, or from any one of the four rows), these six it blocks redden:

  • :76-79 sum literal equals ADDITIVE ∪ BOOLEAN
  • :81-83 avg literal equals NUMERIC ∪ BOOLEAN
  • :85-88 min and max literals equal NUMERIC ∪ TEMPORAL ∪ BOOLEAN
  • :108-114 BOOLEAN_VALUE_TYPES literally equals ['boolean','toggle'] and each of the four rows' boolean members equals that set
  • :131-136 predicate accepts all four aggregates over both spellings
  • :138-151 cross-pin: the set of functions over flag in AGGREGATION_CASES equals all six, and each such function is accepted for boolean and toggle

The cross-pin also reddens in the other direction — if someone later removes the flag cases from AGGREGATION_CASES (option B), :144-146 fails. The old refusal pins are gone (:153-159 now lists only computed/text/structured types; :206-213 keeps only the string-class override). The six-test count the PR body reports for its ablation is consistent with the file; I could not execute it locally (no node_modules in this container), so execution rests on CI, where the spec suite runs inside Test Core, all six shards green.

Findings (none blocking)

  • F1 — toggle is not directly pinned by the conformance suite. Every harness declares flag as type: 'boolean'; no harness has a toggle column. The cross-pin's comment (test.ts:139-142, "each case over it is a pair [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 pins on six backends") is accurate for boolean and over-attributes for toggle. The toggle cells are still sound: toggle ∈ BOOLEAN_VALUE_TYPES (field-value.zod.ts:68-70), and driver-sql collects toggle into booleanFields alongside boolean (sql-driver.ts:9636, column mapping :2364), so the Postgres cast(?? as int) at :8685-8691 fires for it. The ruling names toggle explicitly, so the table is right to carry it. Expectation for this PR: none required ("nothing else moves" forbids touching AGGREGATION_CASES); an optional one-line comment fix at test.ts:139-142 saying the toggle half rests on the boolean-class membership rather than a suite row. A toggle conformance row is a driver-family card, not this one.
  • F2 — "six backends" carries the suite's own caveat. aggregation-conformance.ts:97-105 records that the driver-mongodb cell is lowering-only (in-process evaluator, "does not answer 'does MongoDB agree?'"). The head TSDoc (:59-65) and the changeset repeat "enrolled on six backends" without that qualifier. Informational; it is not a table-cell claim and it restates the suite's own enrolment list.
  • F3 — bucket pins were weakened and re-strengthened correctly. The numeric pin (test.ts:90-97) and temporal pin (:99-106) now subtract the boolean class before comparing; the new boolean-bucket pin (:108-114) holds the class literally, so a type joining any of the three field-value.zod classes elsewhere still reddens this file. Verified, no action.
  • F4 — surface deviation, accepted. Parent changeset edit (see §4): pending, prose-only, declared.
  • F5 — CI. 38 check runs at 499d718: 30 success, 7 skipped (opt-in / path-filtered), 0 failure; Lint & Repo Gates still in_progress at time of review (running ~25 min). This verdict is conditional on that gate finishing green; the rest of the matrix — Build Core, all six Test Core shards, all Type Check jobs, Temporal Conformance (live PG + MySQL), Check Changeset ×2, Governed Surface Queue Guard, Spec property liveness — is green.

No approval, merge, label or file action taken by this seat.


Generated by Claude Code

…rs changeset; derive the flag-case vocabulary pin from AggregationFunction (#16685)

Contract-review patch round. The changeset claimed dist/*.d.ts was
byte-identical; it is not - the rewritten module TSDoc ships in
dist/data/index.d.ts. It now states what holds: the exported declarations
are unchanged, the private BOOLEAN_AGGREGATE_FIELD_TYPES constant is absent
from the bundle, and api-surface / export-origins are untouched. The test
header claims only what the cross-pin reaches (the boolean axis) and the
flag-case vocabulary is derived from AggregationFunction.options instead of
a literal six-member list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…— dist/*.d.ts and the bundles' export lists, not the bundle (#16685)

Contract-review patch round 2. The constant does ship inside the bundles as
a non-exported binding, so "absent from the bundle" over-claimed; the
changeset now says only what was measured: absent from dist/*.d.ts and
from the bundles' export lists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 04:37
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit ed7243d Sep 8, 2026
39 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16685-boolean-aggregate-members branch September 8, 2026 05:12
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 17, 2026
…eld type cannot carry, and reconcile the storage-form annotations to one measured statement (objectstack-ai#16778)

* feat(service-analytics)!: refuse an aggregate a measure's field type cannot carry

WIP — compile-leg refusal + the four reconciled datetime-storage annotations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

* wip: changeset + adr-0087 ledger entry

* fix(service-analytics): scope the compile-leg refusal to temporal source fields

The full table refuses `min`/`max` over the string classes and the boolean rows,
both of which this platform answers on purpose and pins with tests (objectstack-ai#15768,
maintainer ruling objectstack-ai#11152). Executing those is a product judgement that belongs to
objectstack-ai#16099; the temporal rows carry no such collision and are the ones this card is
about. Re-points the two `measure-result-type.test.ts` fixture measures that
aggregated a datetime column, and corrects the module header that recorded the
missing refusal as an open finding.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

* docs(changeset,spec): scope the breaking declaration to the temporal class it actually withdraws

The compile leg was scoped to TEMPORAL source fields in 80ec9f2, but the
changeset and the ADR-0087 ledger entry still described the pre-scoping
full-table gate. A breaking-change record that overstates what changed tells
every consumer reading the release notes that behaviour was withdrawn which
was not.

F1 — the breaking declaration:
- changeset FROM/TO: the two `percent` rows dropped (`sum` x `percent` is a
  table row this leg does not execute; `avg` x `percent` was never a
  migration at all), and the surviving `avg` row widened to name all three
  temporal members it does refuse.
- changeset: a new scope section states the temporal scope outright, and that
  the string rows sit under objectstack-ai#16785 (ruled C - the table is to be AMENDED to
  accept them), the boolean rows were settled as ACCEPT by objectstack-ai#16685 / objectstack-ai#16750,
  and `sum` x `percent` is not executed here.
- ledger `surface`: scoped to the temporal class; "sum over a percent" removed.
- ledger `replacement`: the "`avg` for a `percent`" prescription dropped with
  the surface rows it belonged to.
- ledger `acceptanceCriteria`: qualified to a measure over a
  `date` / `datetime` / `time` field, and says outright that a field of any
  other class is neither refused nor certified by this leg.
- `registry.ts` REGENERATED with `pnpm --filter @objectstack/spec
  gen:migration-registry`, never hand-edited; two consecutive runs are
  byte-identical and the diff is confined to this entry's block.
- also corrected: the changeset said "four contradictory annotations"; the
  sweep reconciled seven source sites plus two test narratives.

F2 — the boolean collision is settled, so stop narrating it as live. objectstack-ai#16685
was ruled A and objectstack-ai#16750 added `boolean` / `toggle` to the four arithmetic /
order rows, so the table ACCEPTS them. Reworded in `dataset-compiler.ts`'s
scope docblock, the refusal suite's module header, and `measure-result-type.ts`
(whose boolean paragraph still referred a missing refusal onward). All three
now also record objectstack-ai#16785 C for the string rows.

F3 — the ledger `reason` presented both dialect halves as measured alike. The
SQLite half is pinned by a live `sql.js` suite; the Postgres 42883 half was
measured in-session and is pinned by nothing. Said so where it is stated.

F4 — the scope-boundary test asserted `isAggregateCompatibleWithFieldType(
'min', 'text') === false`, a verdict objectstack-ai#16785 C is about to amend. Dropped: the
case now pins only what this PR owns - a non-temporal field is not judged, so
the measure compiles and SQL is emitted. Refutability is carried by a second
case on `sum` x `text`, a row no ruling is moving, plus a non-vacuity
assertion that SQL reached the driver in both.

F5 — the changeset now names the two uncovered faces: `/analytics/query` and
any `compileDataset` caller wiring no `declaredFieldType` probe.

Refs objectstack-ai#16737. Review: PR objectstack-ai#16778 contract review, comment 5580295870.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…commits that decided them (objectstack-ai#20729)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the eighth stage of the `domain:services` lane of the
dead-citation sweep. It covers
`packages/services/service-analytics/src/**` and nothing else. By the
seat's census at the claim (`5899485578`), it is the largest package in
the lane that no in-flight work holds. Later stages cover the other
packages, so this PR says `Part of` and the card stays open.

Every comment or docblock site in scope that cited a tracker number
answering 404 has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123), by the method of stages 1 to 7 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`, PR objectstack-ai#20634 as `4d04b6be3`, PR
objectstack-ai#20658 as `9a4b2bb38`, PR objectstack-ai#20693 as `0e9ad74fb`, PR objectstack-ai#20708 as
`9b384f63a`, PR objectstack-ai#20717 as `cbaf04c1f`). That is **76 sites on 76 lines
in 22 files, covering 14 numbers**:

- 42 census sites (every census site this package has);
- 34 sites in test comments, which the census defers.

The raw scan found no dead site the gate's grammar cannot see (see
Acceptance notes), so there is no third class this time.

Each rewritten line now cites the commit in `origin/main` history that
decided what the line describes, and says in its own words what was
decided: **13 distinct shas**. No number in this package has an ADR or
ruling record of its own in the repository (a grep of `docs/adr/` for
all 14 finds none, and a grep of the rest of `docs/` finds none either),
so every anchor is a commit, per ruling C's order. No number was
dropped.

Only comments changed. Every touched source file keeps its line count
(78 lines out, 78 in, over 22 files), so no line citation into these
files moves. 2 of those 78 lines hold no dead citation: they are reflow
lines, listed under Wordings below. No code token moves (see the guard
below).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces: `objectstack-ai#10861` (5 lines), `objectstack-ai#12776` (3),
`objectstack-ai#10413` (2), `objectstack-ai#16750` (2), and `objectstack-ai#10759`, `objectstack-ai#11152`, `objectstack-ai#5716` and the
decision-batch ordinal `objectstack-ai#59` once each. Each tracker number among them
resolves. Over the whole diff, added minus removed is 0 or negative for
every number, and no number is new to the diff. No PR number is the
citation on an added line: the two `PR #N` spellings in scope became
their pull request's squash commit, and `objectstack-ai#16750` stays only as the
convenience link beside `ed7243d52`, on the line it already stood on.

Eight dead sites are left on purpose, all of them test strings (see the
list below).

One more file: a `patch` changeset for `@objectstack/service-analytics`,
because the rewritten docblocks and inline comments ship (see Changeset
below).

The `AnalyticsResultWithDrill` type and its four sidecar members are not
touched: its docblocks carry no dead number (`objectstack-ai#20644`, `objectstack-ai#3214` and
`objectstack-ai#1752` all resolve).

## Census: `service-analytics`, before and after

**Instrument (A1).** The gate's own `node
scripts/check-issue-citations.mjs --census --json`, read-only and
unchanged. The count below is its `allocated-but-absent` findings under
`packages/services/service-analytics/`. Each run counts as a reading
only because its board frontier equals the newest issue number, read by
a separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
service-analytics sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `cbaf04c1f`, run 2026-09-29T21:41:53Z to 21:45:05Z |
enumerated, 186 pages, frontier objectstack-ai#20721 (newest objectstack-ai#20721 before and after),
18,548 numbers | 1,161 | **42** | 42 | 10 | 10 |
| after | head `967d73531`, run 21:55:23Z to 21:58:36Z | enumerated, 186
pages, frontier objectstack-ai#20723 (newest objectstack-ai#20723 before and after), 18,550 numbers
| 1,119 | **0** | 0 | 0 | 0 |

The before count matches the seat's census at the claim and A1 (42
sites): the two comments PR objectstack-ai#20712 rewrote in `analytics-service.ts` did
not move it. The whole-repo drop is 42, exactly this diff's census
sites. The `resolves` tally is 32,991 in both runs, and
`resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did
not move either. The after run was taken on `967d73531`; the head
`82d2b40b2` adds only the changeset. No run was truncated or discarded:
both enumerations read 186 pages at the newest frontier.

**Supplementary instrument, the whole scope.** The census does not read
test files or strings, and this stage's scope includes test comments. So
a second reading runs the gate's own exported `extractCitations`
(whole-file and comment-prose projections) and `namesThisRepository`
over every `.ts` file under `service-analytics/src` (162 files). It
takes its verdicts from the before census's own board reading rather
than from a second enumeration: a number is dead when that census
reported it `allocated-but-absent`, and alive when that census judged it
on this board anywhere (its `--list` extraction) and did not report it.
The 21 numbers the census never saw, because they stand only in test
files or strings here, were read one by one on the issues endpoint: 17
answer 200, and `objectstack-ai#16778`, `objectstack-ai#16860`, `objectstack-ai#16918` and `objectstack-ai#17125` answer 404.

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `cbaf04c1f` | 3,514 | **84** | 42 | 34 | 0 | 8 |
| after, `967d73531` | 3,438 | **8** | 0 | 0 | 0 | 8 |

Its src-comment column equals the census's 42, which is the control on
the second instrument. The 3,410 live citations and the 20 cross-repo
citations are the same in both readings, and the drop of 76 citations is
exactly the rewritten sites. A third, raw reading (every `#` followed by
2 to 6 digits, whatever surrounds it) finds 3,598 occurrences and 84
dead before, 3,522 and 8 after; its residue equals the gate's residue
site for site, and it sees no dead site beyond the gate.

## Per-number table

Sites and files count every dead occurrence in scope at the base
(comments and strings, tests included). `rewritten / left` counts the
sites rewritten and the sites left. Each anchor was read in its message
and diff, not only its subject, and `git blame` at the base puts every
rewritten line in its anchor commit or in a later commit that descends
from it (`merge-base --is-ancestor` exit 0 for each pair).

| number | sites / files | rewritten / left | anchor: what it decided |
|---|---|---|---|
| `objectstack-ai#11461` | 20/2 | 19/1 | `399ecad58`: a cross-object leaf in one
measure's own `filter` (the third producer, lowered onto
`aggregations[].filter`) is refused on both ObjectQL doors with
`INVALID_FIELD` / 400 naming the measure, folded into the one member
view, with insertion order keeping every earlier refusal's message. The
last line of its message names `objectstack-ai#11461` as the card it settles. New to
the sweep |
| `objectstack-ai#17130` | 17/5 | 13/4 | `54b3d1d4a` (PR objectstack-ai#17336): the row-scope
resolution refusals carry `READ_SCOPE_COMPILE_FAILED` / 500 through one
constructor, so `queryDataset`'s catch re-throws them instead of reading
their words, every message byte-unchanged; plus the source-derived
wording-collision guard. Named in its diff only (18 added lines carry
the tag). New to the sweep |
| `objectstack-ai#17124` | 12/8 | 10/2 | `86c505286` (PR objectstack-ai#17593):
`explicitDateRangeWindow` is the one reading of `dateRange`'s array arm
on all four faces, and an array that is not two string bounds is refused
with `ANALYTICS_DATE_RANGE_UNRECOGNIZED` / 400. Named in its diff only
(its changeset file is `17124-daterange-array-arm-arity.md`). New to the
sweep |
| `objectstack-ai#12209` | 10/5 | 10/0 | `017130a09` (PR objectstack-ai#12318): a custom-SQL measure
is refused on the ObjectQL aggregate path with `INVALID_FIELD` / 400,
keyed on the `EXPRESSION_METRIC_TYPES` partition shared with
`NativeSQLStrategy`. Its message records the two failure modes the lines
describe (`driver-sql` blaming a `function` key, the in-memory evaluator
answering `null` per bucket). Named in its diff only. New to the sweep |
| `objectstack-ai#16778` | 5/1 | 4/1 | `357f4992b`: the compile-leg refusal of an
aggregate a datetime measure's field type cannot carry, scoped to
temporal source fields. The squash commit of the pull request that was
`objectstack-ai#16778`; its subject carries the number. New to the sweep |
| `objectstack-ai#12940` | 4/2 | 4/0 | `aa16721b6` (PR objectstack-ai#13361): this package's
consumer-local `executeAggregate` config mirrors (the plugin options and
`AnalyticsServiceConfig`) narrow `aggregations[].method` to
`AggregationFunction`, after `objectstack-ai#12776` narrowed the contract. Named in
its diff only. New to the sweep |
| `objectstack-ai#17015` | 4/2 | 4/0 | `0da638cd9`: the closed `dateRange` preset
vocabulary is lowered once and the rest refused, the `[range, range]`
fallback is removed from the faces it reached, and the shared
conformance kit holds them. The squash commit of the pull request that
was `objectstack-ai#17015`. New to the sweep |
| `objectstack-ai#16860` | 3/1 | 3/0 | `041d9fdc6`: the object-level read grant is
asked at the analytics door, and its bridge to the `security` service
resolves an explicit three-way (absent admits; throwing or method-less
denies at `error`, finding F3 in its message). The squash commit of the
pull request that was `objectstack-ai#16860`. New to the sweep |
| `objectstack-ai#12248` | 2/1 | 2/0 | `8425c17cc`: the five ruled engine members,
`getDriverForObject?` and `resolveEffectiveDatasource` among them,
adopted onto `IDataEngine`, and `getObject` typed. Its subject names it.
Stage 5's and the spec stage's anchor |
| `objectstack-ai#16685` | 2/2 | 2/0 | `ed7243d52` (PR objectstack-ai#16750): `boolean` / `toggle`
accepted for `sum` / `avg` / `min` / `max` in the aggregate × field-type
table, holding maintainer ruling `objectstack-ai#11152`. Its subject names it. The
spec stage's anchor |
| `objectstack-ai#17125` | 2/2 | 2/0 | `5d12b16e7`: the row-scope bridge tells an
absent security service from a broken one, so a broken one refuses the
query. The squash commit of the pull request that was `objectstack-ai#17125` (404 on
the pulls endpoint too). New to the sweep |
| `objectstack-ai#16918` | 1/1 | 1/0 | `5d12b16e7`: the same commit. Its changeset's
headline names `objectstack-ai#16918` as the card it answers, and its diff writes the
line (`admission-bridge-resolution.test.ts:120`) |
| `objectstack-ai#6123` | 1/1 | 1/0 | `59d1933f9`: `err.code` lands at `error.code`,
not `error.details.code`; the commit that wrote this very line. The
`runtime` stage's anchor |
| `objectstack-ai#13279` | 1/1 | 1/0 | `6a180e42d`: permission-store read failures
fail loud, and the same commit renames
`metadata/src/utils/schema-sync-errors.ts` to
`packages/types/src/driver-error-classification.ts`, the move the line
describes. The anchor of stages 2, 5 and 6, and of the `types`, `rest`
and `runtime` stages |

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each of the 13), and every one is an
ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13;
control leg: stage 1's landing `422db788a` exit 0; the history is
complete, `--is-shallow-repository` false, 15,135 commits). Each of the
14 numbers answers 404 on the issues endpoint, read one by one;
`objectstack-ai#16778`, `objectstack-ai#16860`, `objectstack-ai#17015` and `objectstack-ai#17125` answer 404 on the pulls
endpoint too.

## Wordings to check

- **Bracket tags.** `[#N]` became `[commit SHA]`, as in stage 7;
`[objectstack-ai#10861 / objectstack-ai#11461]` and `[objectstack-ai#10861, objectstack-ai#11461]` keep the live `objectstack-ai#10861` beside
the new sha.
- **The boolean rows, `measure-result-type.ts:115-116` and
`aggregate-datetime-measure-refusal.test.ts:65-66`.** 「objectstack-ai#16685 ruled A,
landed as objectstack-ai#16750」 and 「objectstack-ai#16685 was ruled A and objectstack-ai#16750 added」 became
「commit ed7243d (objectstack-ai#16750) added those rows」 and 「commit ed7243d
(objectstack-ai#16750) added」. 「ruled A」 named an option on the dead card;
`ed7243d52`'s message records the decision itself. Line 116 of the first
file and line 66 of the second are the 2 reflow lines: each keeps the
`objectstack-ai#16750` it already carried.
- **PR numbers, `read-scope-resolution-envelope.test.ts:25` and
`refusal-wording-collision.test.ts:21`.** 「PR objectstack-ai#17125's refusal」 became
「Commit 5d12b16's refusal」, the pull request's squash commit.
- **`read-scope-refusal.ts:29`.** 「objectstack-ai#17130 exists to remove it」 became
「commit 54b3d1d was made to remove it」, the form stage 6 used.
- **`refusal-wording-collision.test.ts:49`.** 「the exact move objectstack-ai#17130
forbids」 became 「the exact move commit 54b3d1d ruled out」; its message
says the fix is the declaration, not a luckier string.
- **`read-scope-resolution-envelope.test.ts:161`.** The verb after the
number moved from present to past tense with the sha.
- **`measure-expression-both-strategies.test.ts:45` and `:166`.**
「deleting the objectstack-ai#12209 arm in」 became 「deleting the arm commit 017130a
added in」, and 「every objectstack-ai#12209 refusal」 became 「every custom-SQL refusal
(commit 017130a)」.
- **`dataset-executor.ts:609`.** 「objectstack-ai#17015's kit」 became 「commit
0da638c's kit」, the conformance kit that commit built.
- **`plugin.ts:116`.** 「and in objectstack-ai#12209:」 became 「and in commit
017130a:」, whose message records the two ways the engine failed.
- **`analytics-service.ts:238`.** 「objectstack-ai#13279 moved it there」 became 「commit
6a180e4 moved it there」; that commit's diff is the rename.

## The 8 sites left

- **Test strings, 8 sites**, left as stages 1 to 7 left theirs, all
`describe` / `it` titles:
  - `crossobject-conjunct-refusal.test.ts:589` (`objectstack-ai#11461`);
  - `aggregate-nontemporal-measure-refusal.test.ts:243` (`objectstack-ai#16778`);
  - `date-range-array-arm-arity.test.ts:213` and `:294` (`objectstack-ai#17124`);
- `read-scope-resolution-envelope.test.ts:155`, `:199` and `:226`, and
`refusal-wording-collision.test.ts:336` (`objectstack-ai#17130`).
- There is no operator string, generated file or quoted ruling carrying
a dead number in this package. It has no generated file at all.

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as
trivia and JSDoc nodes never visited, base `cbaf04c1f` against head.
Template literals are therefore read in context. It ran over all 22
touched `.ts` files.

- Real run: 26,705 base leaf tokens, **0 files with a token change**
(exit 0).
- Comment control in `plugin.ts` (「refusal buys is in」 to 「refusal earns
is in」): 0 files changed, as expected (exit 0).
- Positive control, a code token added in `plugin.ts` (`field: a.field,`
given `as string`): DIFFER (exit 1).
- Positive control, one digit changed inside a kept test title
(`date-range-array-arm-arity.test.ts:213`): DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs`, and each
landed (anchor 1 to 0, blob changed). Each restore was proven
byte-identical to the HEAD blob (`ad3dc9fff4d3`, `a606ffbb6ead`), with
`git diff HEAD` empty and a clean tree afterwards.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/service-analytics`
(`.changeset/20596-service-analytics-provenance-anchors.md`) is
included. Its body is stage 7's, word for word, with the package name
changed.

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After the build (a cache miss for this package, so
`dist` is this head's source), the rewritten comments reach `dist`:
`399ecad58` 6 times in each of `dist/index.js`, `index.cjs`,
`index.d.ts` and `index.d.cts`; `86c505286` twice in each JS file and
once in each declaration file; `54b3d1d4a` once in all four; `aa16721b6`
once in each JS file and twice in each declaration file; `017130a09`
once in each JS file. Positive controls: the unchanged line 「none of the
coverage: a compiled measure's own」, in the same docblock as the shipped
rewrite at `objectql-strategy.ts:744`, is found once in each of the four
files, and the unchanged line 「back into line. Widening it here again
would not be a local matter」 beside the shipped rewrite at
`analytics-service.ts:559` once in each declaration file. A
never-written negative phrase appears nowhere in `dist`. None of the 14
dead numbers is left anywhere in `dist`.

## Gates (head `82d2b40b2`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
(self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0:
the diff-scoped run judged 11 citations across 10 files; 10 resolve and
1 resolves as a pull request (`objectstack-ai#16750`, the convenience link that
already stood on its line).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `82d2b40b2` derived 62 commands:
all 56 derived at dispatch, plus `check:engine-double-contract`,
`check:objectql-double-limit`, `check:query-options-erasure`,
`check:type-check-coverage`, `check:type-check-debt` and
`check:where-matcher`. Each ran with its exit code captured before any
pipe, and all 62 exit 0. `--ran`, fed each command with its exit code,
reports 62 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. A
full `turbo run build` of `./packages/*` and `./packages/*/*` ran first
under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an
unbuilt workspace.
- **Roster families the derivation lists outside its commands** (their
rosters sit in directories this diff touches): `node
scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm
check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit
0.
- **Tests and typecheck, under the verify lock:**
- `pnpm --filter @objectstack/service-analytics test`: 137 files pass
and 3,216 tests pass. That is every test file in the package, the 12
touched ones included.
- `pnpm --filter @objectstack/service-analytics typecheck` exits 0 (`tsc
--noEmit` on `tsconfig.json`). `--listFiles`: the program holds all 162
files under `src/`, the 137 test files and all 22 touched files
included.
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 22 touched `.ts` files gives 22 files, 0 errors and 0
warnings. All 22 are in eslint's own population (`isPathIgnored` is
false for each; a `dist` file, as the control, is ignored).
`eslint.config.mjs` never enables type-aware linting (no
`parserOptions.project`, as its own lines 327-328 state), so a comment
edit here cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is CI's run.
- **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of
the 23 changed files for control bytes finds none.

## Acceptance notes

- **The gate-invisible spellings, grepped as the claim asked.**
`CITATION_RE` refuses a hyphen after the digits and a `/` before the `#`
(objectstack-ai#20636), and `NON_CITATION_HEADS` excuses a number after the word
「option」. In this package:
- `#N-word`: 8 lines by a plain grep, and 7 once a hyphen before the `#`
is excluded too, which is the claim's 7. The eighth is
「pre-objectstack-ai#10413-phase-2」 (`execution-context-bridge.test.ts:223`). The
numbers, `objectstack-ai#10413`, `objectstack-ai#5298`, `objectstack-ai#13570` and `objectstack-ai#13640`, all resolve.
- `#A/#B`: 29 lines, the claim's 29, over 28 distinct numbers. All
resolve; `objectstack-ai#2149`, which the census never judged, was read on its own.
  - `option #N`: none.
So nothing here needed a rewrite beyond the gate, and the raw scan
agrees.
- **「This card」 phrases are left.** 113 lines in 39 files of this
package speak of 「this card」, 「that card」 or 「the card」. They carry no
number, neither instrument sees them, and most sit in blocks whose
numbers still resolve. Stage 7 rewrote two such lines as lost referents;
here none is changed, because the phrase runs through the whole package
and rewriting a subset would be arbitrary.
- **Prose that names `queryDataset`'s catch, not changed.** Nine comment
lines say `queryDataset`'s catch. Since `10c36cc43` that catch sits in
the private `answerDataset`, whose docblock calls it the body of
`queryDataset`, so the lines still hold at the level of the public
method. This is not a dead citation, so it is outside this stage.
- **The census instrument did not truncate in this stage.** Both
enumerations read 186 pages at the newest frontier.
- **Anchors the next stages can reuse**, each checked here: `objectstack-ai#11461` →
`399ecad58`; `objectstack-ai#17130` → `54b3d1d4a`; `objectstack-ai#17124` → `86c505286`; `objectstack-ai#12209` →
`017130a09`; `objectstack-ai#16778` → `357f4992b`; `objectstack-ai#12940` → `aa16721b6`; `objectstack-ai#17015` →
`0da638cd9`; `objectstack-ai#16860` → `041d9fdc6`; `objectstack-ai#17125` and `objectstack-ai#16918` →
`5d12b16e7`.
- **Base.** The branch is on `main` at `cbaf04c1f`. `main` has since
moved four commits (`3711e0b76`, `61455de27`, `6afccda5a`, `671d4c164`).
They touch `packages/spec`, `packages/metadata/package.json`,
`pnpm-lock.yaml`, docs and changesets, and no file under
`service-analytics` or in this diff, so no merge was taken; the merge
queue rebuilds on the merged generation. One of them, `671d4c164`,
declares the four drill-through sidecars on `AnalyticsResult` in the
spec. This diff leaves the local `AnalyticsResultWithDrill` untouched,
as the claim requires.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants