From 967d73531a1324f4aa08a3ce0be60d91b9c5c7ea Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 21:53:40 +0000 Subject: [PATCH 1/2] docs(service-analytics): re-anchor the dead tracker citations to the commits that decided them Every comment or docblock site under packages/services/service-analytics/src that cited a tracker number answering 404 now cites the commit in this repository's history that decided what the line describes (ruling C+D, form C): 76 sites on 76 lines in 22 files, 14 numbers, 13 anchor commits. Two further lines are reflowed; every file keeps its line count. Comments only: no code token moves, and the 8 dead numbers inside test titles and test strings are left as they are. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude --- .../admission-bridge-resolution.test.ts | 2 +- ...gregate-bridge-function-vocabulary.test.ts | 4 +-- ...aggregate-datetime-measure-refusal.test.ts | 4 +-- ...regate-nontemporal-measure-refusal.test.ts | 8 +++--- .../analytics-date-range-conformance.test.ts | 4 +-- .../crossobject-conjunct-refusal.test.ts | 18 ++++++------- .../date-range-array-arm-arity.test.ts | 2 +- ...measure-expression-both-strategies.test.ts | 6 ++--- .../src/__tests__/objectql-daterange.test.ts | 2 +- .../read-scope-bridge-resolution.test.ts | 6 ++--- .../read-scope-resolution-envelope.test.ts | 6 ++--- .../refusal-wording-collision.test.ts | 8 +++--- .../src/analytics-service.ts | 12 ++++----- .../service-analytics/src/dataset-executor.ts | 12 ++++----- .../src/date-range-array-arm.ts | 2 +- .../src/measure-result-type.ts | 4 +-- .../services/service-analytics/src/plugin.ts | 18 ++++++------- .../src/preview-evaluator.ts | 2 +- .../src/read-scope-refusal.ts | 4 +-- .../service-analytics/src/read-scope-sql.ts | 2 +- .../src/strategies/native-sql-strategy.ts | 4 +-- .../src/strategies/objectql-strategy.ts | 26 +++++++++---------- 22 files changed, 78 insertions(+), 78 deletions(-) diff --git a/packages/services/service-analytics/src/__tests__/admission-bridge-resolution.test.ts b/packages/services/service-analytics/src/__tests__/admission-bridge-resolution.test.ts index 2c69b737790..fbe1f8e8196 100644 --- a/packages/services/service-analytics/src/__tests__/admission-bridge-resolution.test.ts +++ b/packages/services/service-analytics/src/__tests__/admission-bridge-resolution.test.ts @@ -117,7 +117,7 @@ async function bootAnalytics(security?: () => unknown) { * A working security service's ROW-SCOPE half, carried by every double below * that is meant to represent one. * - * `getReadFilter` is a REQUIRED member of `ISecurityService`, and since #16918 + * `getReadFilter` is a REQUIRED member of `ISecurityService`, and since commit 5d12b16e7 * the ROW-SCOPE bridge in the same `plugin.ts` refuses the query when the * registered service does not expose it — the sibling three-way of the one * this file measures. `undefined` is that method's documented answer for "no diff --git a/packages/services/service-analytics/src/__tests__/aggregate-bridge-function-vocabulary.test.ts b/packages/services/service-analytics/src/__tests__/aggregate-bridge-function-vocabulary.test.ts index ba9c058e421..573727b6271 100644 --- a/packages/services/service-analytics/src/__tests__/aggregate-bridge-function-vocabulary.test.ts +++ b/packages/services/service-analytics/src/__tests__/aggregate-bridge-function-vocabulary.test.ts @@ -10,7 +10,7 @@ * `EngineAggregateOptions.aggregations[].function`) is the SIX-value * `AggregationFunction`. Nothing compiled the two against each other, so the * bridge forwarded whatever string reached it — and the engine then failed in - * the two ways #12209 documents: `driver-sql` blaming a `function` key the + * the two ways commit 017130a09 documents: `driver-sql` blaming a `function` key the * author never wrote, or the in-memory evaluator answering `null` for every * bucket under the author's own measure name (the #4157 class). * @@ -21,7 +21,7 @@ * * The reachable producer of a non-aggregate method — a custom-SQL measure * (`AggregationMetricType` `number`/`string`/`boolean`) — is refused earlier - * and caller-facing by `ObjectQLStrategy.resolveMeasureAggregation` (#12209, + * and caller-facing by `ObjectQLStrategy.resolveMeasureAggregation` (commit 017130a09, * `INVALID_FIELD` / 400). Anything still arriving at the bridge is host drift * (an unparsed cube object, our own drift), which `dataset-refusal.ts`'s module * header assigns to the bare-`Error`, undeclared-500 tier — the same tier it diff --git a/packages/services/service-analytics/src/__tests__/aggregate-datetime-measure-refusal.test.ts b/packages/services/service-analytics/src/__tests__/aggregate-datetime-measure-refusal.test.ts index 5da13b049cf..9015671eeab 100644 --- a/packages/services/service-analytics/src/__tests__/aggregate-datetime-measure-refusal.test.ts +++ b/packages/services/service-analytics/src/__tests__/aggregate-datetime-measure-refusal.test.ts @@ -62,8 +62,8 @@ * `min` × `text` — which this file pinned as compiling, deliberately and * conditionally on a ruling landing first — is now refused beside `sum` × `text`. * - * The BOOLEAN rows were never a collision in any scope: #16685 was ruled A and - * #16750 added `boolean` / `toggle` to the `sum` / `avg` / `min` / `max` rows + * The BOOLEAN rows were never a collision in any scope: commit ed7243d52 + * (#16750) added `boolean` / `toggle` to the `sum` / `avg` / `min` / `max` rows * (maintainer ruling #11152 — booleans aggregate as numbers on every backend), * so the table ACCEPTS them and nothing refuses them anywhere. The non-temporal * population is pinned in `aggregate-nontemporal-measure-refusal.test.ts`; this diff --git a/packages/services/service-analytics/src/__tests__/aggregate-nontemporal-measure-refusal.test.ts b/packages/services/service-analytics/src/__tests__/aggregate-nontemporal-measure-refusal.test.ts index 410de8c3405..286208619e2 100644 --- a/packages/services/service-analytics/src/__tests__/aggregate-nontemporal-measure-refusal.test.ts +++ b/packages/services/service-analytics/src/__tests__/aggregate-nontemporal-measure-refusal.test.ts @@ -7,7 +7,7 @@ * * ## What this card found, driven before anything was written * - * #16778 landed the compile leg of the director ruling (decision batch #59: + * Commit 357f4992b landed the compile leg of the director ruling (decision batch #59: * one compatibility table in `@objectstack/spec`, two refusal legs) SCOPED to * temporal source fields. The residual was "every other non-temporal pair the * table refuses", and the dispatch required it be driven rather than read, @@ -23,7 +23,7 @@ * * The control is what makes the 107 a reading of the tree rather than of a * blind harness: the SAME service, door and `sourceFieldMeta` hook sees the - * three pairs #16778 enforces refused, with no statement emitted. + * three pairs commit 357f4992b enforces refused, with no statement emitted. * * ## Why the scope is an AGGREGATE class and not "the rest of the table" * @@ -84,7 +84,7 @@ const FIELD_TYPES: Record = { embedding: 'vector', // refused for `sum` only — a rate does not add (`isIncoherentAggregate`) win_rate: 'percent', - // the temporal class #16778 already enforced, kept as the continuity control + // the temporal class commit 357f4992b already enforced, kept as the continuity control submitted_at: 'datetime', // accepted controls cycle_days: 'number', @@ -176,7 +176,7 @@ describe('#16099 — the pairs this leg refuses are the TABLE\'s, not this packa } } expect(refusedByTable).toBe(155); - expect(temporal).toBe(6); // #16778's — `sum`/`avg` over the temporal class + expect(temporal).toBe(6); // commit 357f4992b's — `sum`/`avg` over the temporal class expect(deriving).toBe(75); // #16099's — `sum`/`avg` over everything else expect(selecting).toBe(74); // #17560's — `min`/`max`, 42 string + 32 non-string expect(temporal + deriving + selecting).toBe(refusedByTable); diff --git a/packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts b/packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts index 24e23b9f689..ab16a0ad81a 100644 --- a/packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts +++ b/packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts @@ -135,7 +135,7 @@ async function lowerViaPreview(range: string | readonly string[]): Promise it('⛔ CONTROL — the CALLER\'s explicit window is still shifted bound for bound', async () => { // ⭐ Without this, every assertion above is satisfied by a face that // rewrote the array arm too. The answer is byte-identical to the one - // #17124 pinned before this change. + // commit 86c505286 pinned before this change. expect(await comparePasses(['2026-01-01', '2026-01-31'], 'previousPeriod')).toEqual([ [['2026-01-01', '2026-01-31']], [['2025-12-01', '2025-12-31']], diff --git a/packages/services/service-analytics/src/__tests__/crossobject-conjunct-refusal.test.ts b/packages/services/service-analytics/src/__tests__/crossobject-conjunct-refusal.test.ts index d93ce427a31..7d634b6d6aa 100644 --- a/packages/services/service-analytics/src/__tests__/crossobject-conjunct-refusal.test.ts +++ b/packages/services/service-analytics/src/__tests__/crossobject-conjunct-refusal.test.ts @@ -45,7 +45,7 @@ * leaves into the same one member view. Both producers are now judged by one * check, which is why they are pinned in one file. * - * ## [#11461] The third producer, and the door it left open on BOTH doors + * ## [commit 399ecad58] The third producer, and the door it left open on BOTH doors * * #10413 phase 2 added a THIRD route to `engine.aggregate`'s predicate: a * compiled MEASURE's own `filter`, lowered onto that measure's @@ -110,14 +110,14 @@ * predicate — the load-bearing half of ⑤, and the pin a * "refuse every dataset scope" implementation fails * ⑦ a CROSS-OBJECT per-measure `filter` is REFUSED on both doors, naming the - * measure whose declaration holds the leaf (#11461) + * measure whose declaration holds the leaf (commit 399ecad58) * ⑧ an ORDINARY per-measure `filter` still reaches the engine CARRYING its * own `aggregations[].filter`, and a cross-object one on a measure the * query does NOT ask for changes nothing — the two load-bearing halves of * ⑦, and the pins a "refuse every measure filter" and a "refuse on the * dataset's whole `measureFilters` map" implementation each fail * - * ①–④ are #10759's, re-run unchanged; ⑤–⑥ are #10861's; ⑦–⑧ are #11461's. + * ①–④ are #10759's, re-run unchanged; ⑤–⑥ are #10861's; ⑦–⑧ are commit 399ecad58's. */ import { describe, it, expect } from 'vitest'; @@ -131,7 +131,7 @@ const ctxA = { tenantId: 'org_A', userId: 'u_a' } as ExecutionContext; interface Refusal extends Error { code?: string; status?: number; member?: string; param?: string; cube?: string } interface AggSpec { field: string; method: string; alias: string; filter?: Record } /** - * [#11461] `aggregations` is captured too, not just the whole-call `filter`. + * [commit 399ecad58] `aggregations` is captured too, not just the whole-call `filter`. * The third producer never lands in the whole-call filter — it lands on ONE * aggregation's own `filter` — so a harness that only watched `options.filter` * could not have seen this card's defect at all, and ⑧'s "still carries its @@ -195,7 +195,7 @@ const MIXED_SCOPED_SALES: Dataset = DatasetSchema.parse({ }) as Dataset; /** - * [#11461] ONE dataset carrying all three of ⑦/⑧'s directions, so the + * [commit 399ecad58] ONE dataset carrying all three of ⑦/⑧'s directions, so the * distinctions are structural rather than three fixtures that happen to differ. * * `revenue` no filter at all — the neighbour every other measure is @@ -277,7 +277,7 @@ const DATASET_SCOPE_MESSAGE = /cannot evaluate the cross-object filter \("account\.region"\) that dataset "[^"]+" declares at its definition level/; /** - * [#11461] A THIRD distinct message. The two above name where the member came + * [commit 399ecad58] A THIRD distinct message. The two above name where the member came * from; this one has to name something neither can — WHICH MEASURE's own * declaration holds the leaf. A dataset can declare two measures filtering the * same field and mean two different edits, so a rewording that dropped the @@ -564,7 +564,7 @@ describe('[#10861] a CROSS-OBJECT definition-level filter is refused on BOTH doo }); // ──────────────────────────────────────────────────────────────────────────── -// ⑦ + ⑧ [#11461] the CROSS-OBJECT per-measure filter — the third producer +// ⑦ + ⑧ [commit 399ecad58] the CROSS-OBJECT per-measure filter — the third producer // ──────────────────────────────────────────────────────────────────────────── /** @@ -684,7 +684,7 @@ describe('[#11461] a CROSS-OBJECT per-measure filter is refused on BOTH doors', it('the KNOWN-PRESENT control: a cross-object member in the CALLER’s where keeps its own diagnostic on this fixture too', async () => { // The counter-check for every "refused" above, on the SAME cube — so the // refusal ⑦ adds cannot be mistaken for the fixture simply being unable to - // serve anything, and #11461 is shown not to have repainted the refusal + // serve anything, and commit 399ecad58 is shown not to have repainted the refusal // #10759 restored. Refused before this card and after it, with the OTHER // message and with `param: 'where'`. const { execute, generateSql, calls } = await bothDoors('measure_filter_sales', { @@ -700,7 +700,7 @@ describe('[#11461] a CROSS-OBJECT per-measure filter is refused on BOTH doors', // The ordering pin. `filterMemberView` inserts measure-filter leaves FIRST // and `where` last, last write wins — so a member named by the request too // keeps the provenance the caller can act on directly, and every shape - // refused before #11461 keeps the exact message it had. + // refused before commit 399ecad58 keeps the exact message it had. const { execute } = await bothDoors('measure_filter_sales', { dimensions: ['stage'], measures: ['revenue', 'west_revenue'], where: { 'account.region': 'West' }, diff --git a/packages/services/service-analytics/src/__tests__/date-range-array-arm-arity.test.ts b/packages/services/service-analytics/src/__tests__/date-range-array-arm-arity.test.ts index 502a61ef34e..a606ffbb6ea 100644 --- a/packages/services/service-analytics/src/__tests__/date-range-array-arm-arity.test.ts +++ b/packages/services/service-analytics/src/__tests__/date-range-array-arm-arity.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#17124] Every face in this package that reads `dateRange`'s ARRAY arm gives + * [commit 86c505286] Every face in this package that reads `dateRange`'s ARRAY arm gives * an odd-sized array ONE answer — the ADR-0112 refusal — and gives a * two-element window exactly the answer it gave before. * diff --git a/packages/services/service-analytics/src/__tests__/measure-expression-both-strategies.test.ts b/packages/services/service-analytics/src/__tests__/measure-expression-both-strategies.test.ts index 972e57f99ef..2e47015ace9 100644 --- a/packages/services/service-analytics/src/__tests__/measure-expression-both-strategies.test.ts +++ b/packages/services/service-analytics/src/__tests__/measure-expression-both-strategies.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * #12209 — a custom-SQL measure is refused loudly on the ObjectQL path, and + * Commit 017130a09 — a custom-SQL measure is refused loudly on the ObjectQL path, and * BOTH strategies are pinned from one fixture so neither can hide the other. * * #4157 was fixed on one strategy of two: `NativeSQLStrategy` learned to emit @@ -42,7 +42,7 @@ * * ## Dissolution verification, direction predicted BEFORE running * - * Restoring the accepting behaviour (deleting the #12209 arm in + * Restoring the accepting behaviour (deleting the arm commit 017130a09 added in * `ObjectQLStrategy.resolveMeasureAggregation`) must turn the ObjectQL-profile * REFUSAL cases red in the ordinary direction: each asserts the ADR-0112 * envelope (`code`/`status`), the measure's own name in `member` and message, @@ -163,7 +163,7 @@ async function run(query: unknown, profile: 'objectql' | 'native') { return { rows, error, sqls, calls }; } -/** The one wire shape every #12209 refusal must have (ADR-0112 / #5716). */ +/** The one wire shape every custom-SQL refusal (commit 017130a09) must have (ADR-0112 / #5716). */ function expectCustomSqlRefusal( r: { error?: Refusal; sqls: string[]; calls: unknown[] }, member: string, diff --git a/packages/services/service-analytics/src/__tests__/objectql-daterange.test.ts b/packages/services/service-analytics/src/__tests__/objectql-daterange.test.ts index 2ab168a4afa..d3c83231dd6 100644 --- a/packages/services/service-analytics/src/__tests__/objectql-daterange.test.ts +++ b/packages/services/service-analytics/src/__tests__/objectql-daterange.test.ts @@ -197,7 +197,7 @@ describe('ObjectQLStrategy — timeDimensions[].dateRange (#3650)', () => { expect(result.rows).toEqual([{ stage: 'lost', revenue: 200 }]); }); - // [#17124] SUCCEEDS 'narrows rather than vanishes on a one-entry dateRange + // [commit 86c505286] SUCCEEDS 'narrows rather than vanishes on a one-entry dateRange // array', which pinned the point degeneration this card retired. ⛔ Not a // weakening of #3650: that card's complaint was 「no error, just every row // ever recorded」, and the old pin chose the narrower of two WRONG answers diff --git a/packages/services/service-analytics/src/__tests__/read-scope-bridge-resolution.test.ts b/packages/services/service-analytics/src/__tests__/read-scope-bridge-resolution.test.ts index 2567bf3c505..7ce9e4fc72d 100644 --- a/packages/services/service-analytics/src/__tests__/read-scope-bridge-resolution.test.ts +++ b/packages/services/service-analytics/src/__tests__/read-scope-bridge-resolution.test.ts @@ -5,7 +5,7 @@ * must tell apart — the sibling of `admission-bridge-resolution.test.ts`, one * function up in the same file. * - * The object-level bridge was made an explicit three-way (#16860); this one had + * The object-level bridge was made an explicit three-way (commit 041d9fdc6); this one had * the identical shape and still collapsed it: * * ```ts @@ -24,7 +24,7 @@ * has no row restriction on this object". So a deployment whose security * service was broken ran its analytics queries with NO row-level policy at * all, and the only difference from a correctly unrestricted caller was a state - * nothing reported. After #16860 one door of `plugin.ts` failed closed on a + * nothing reported. After commit 041d9fdc6 one door of `plugin.ts` failed closed on a * throwing resolver and its neighbour failed open — and the neighbour is the * one carrying row-level policy. * @@ -213,7 +213,7 @@ describe('analytics row-scope bridge — resolving the "security" service', () = // ── The two doors of this file now agree on a broken provider ────────────── it('refuses a throwing resolver with BOTH auto-bridges live (no door falls open)', async () => { - // With no `admitObjectRead` override the object-level bridge (#16860) + // With no `admitObjectRead` override the object-level bridge (commit 041d9fdc6) // answers first, with `PERMISSION_DENIED`. Pinned so the file-level // property — a broken security service serves no analytics rows through // EITHER door — cannot regress from the other side. diff --git a/packages/services/service-analytics/src/__tests__/read-scope-resolution-envelope.test.ts b/packages/services/service-analytics/src/__tests__/read-scope-resolution-envelope.test.ts index 81eacfb2e9b..1e8b45c6def 100644 --- a/packages/services/service-analytics/src/__tests__/read-scope-resolution-envelope.test.ts +++ b/packages/services/service-analytics/src/__tests__/read-scope-resolution-envelope.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#17130] The row-scope RESOLUTION refusals declare themselves — + * [commit 54b3d1d4a] The row-scope RESOLUTION refusals declare themselves — * `READ_SCOPE_COMPILE_FAILED` / 500 — so no wording can turn one into an empty * chart. * @@ -22,7 +22,7 @@ * the caller — a fail-closed gate rendered as a confident empty chart, with one * `warn` and no exception. * - * PR #17125's refusal propagates today only because its text happens to match + * Commit 5d12b16e7's refusal propagates today only because its text happens to match * none of the six. ⛔ A coincidence, not a construction — and the fix is the * DECLARATION, not a luckier string: every message below is byte-unchanged. * @@ -158,7 +158,7 @@ describe('[#17130] the row-scope resolution refusals declare an ADR-0112 envelop expect(err).toBeInstanceOf(Error); expect(err.code).toBe('READ_SCOPE_COMPILE_FAILED'); expect(err.status).toBe(500); - // ⛔ The message is the site's, untouched — #17130 fixes the declaration. + // ⛔ The message is the site's, untouched — commit 54b3d1d4a fixed the declaration. expect(err.message).toBe('[Analytics] read-scope resolution failed for "x"; query denied (fail-closed).'); }); diff --git a/packages/services/service-analytics/src/__tests__/refusal-wording-collision.test.ts b/packages/services/service-analytics/src/__tests__/refusal-wording-collision.test.ts index 3214390d894..6f6ebcb5631 100644 --- a/packages/services/service-analytics/src/__tests__/refusal-wording-collision.test.ts +++ b/packages/services/service-analytics/src/__tests__/refusal-wording-collision.test.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#17130] No BARE refusal this package raises may be readable as a driver + * [commit 54b3d1d4a] No BARE refusal this package raises may be readable as a driver * saying "the backing table is gone". * * ## The fragility this exists to hold down @@ -18,7 +18,7 @@ * exactly the phrasings a REGISTRY or SECURITY refusal reaches for. So a bare * refusal this package raises on purpose is one wording away from being served * to the caller as "no data": a fail-closed gate turned back into a fail-open - * one by substring match. PR #17125's row-scope refusal propagates today + * one by substring match. Commit 5d12b16e7's row-scope refusal propagates today * because its text happens to match none of the six — a coincidence, not a * construction, and the coincidence is what this file removes. * @@ -35,7 +35,7 @@ * - **The envelope (the primary).** A refusal that declares `code` + `status` * is re-thrown at `hasDeclaredErrorEnvelope` before the sniffer is asked at * all (#5717 defence B), so its wording cannot classify it and its runtime - * interpolations cannot either. #17130's other half gives the two + * interpolations cannot either. Commit 54b3d1d4a's other half gives the two * read-scope refusals that envelope. * - **This guard (the second line).** A refusal that is deliberately bare — * an internal invariant, the families `dataset-refusal.ts`'s header lists @@ -46,7 +46,7 @@ * The split is why an enveloped refusal is deliberately NOT held to the wording * rule: forcing one to be reworded would buy no safety (nothing reads its * words) and would push authors toward picking luckier strings — the exact move - * #17130 forbids. + * commit 54b3d1d4a ruled out. * * ## Where the population comes from — ⛔ never a hand-written list * diff --git a/packages/services/service-analytics/src/analytics-service.ts b/packages/services/service-analytics/src/analytics-service.ts index f5a69cba723..7399de05f0e 100644 --- a/packages/services/service-analytics/src/analytics-service.ts +++ b/packages/services/service-analytics/src/analytics-service.ts @@ -41,7 +41,7 @@ import { assertObjectsReadable, type ObjectReadAdmissionProvider, } from './read-admission.js'; -// [#17130] The ROW-scope half's refusal envelope — the sibling of the +// [commit 54b3d1d4a] The ROW-scope half's refusal envelope — the sibling of the // object-level `readAdmissionDeniedError` above, and the reason a fail-closed // row-scope denial can no longer be re-judged by its wording. import { readScopeUnresolvedError } from './read-scope-refusal.js'; @@ -235,7 +235,7 @@ function isMissingColumnOfRelation(message: string): boolean { * object "O"`, where the "relation" is inside "relationship" and the missing * thing is a RELATIONSHIP, not a table. The anchor is the same pattern the * sibling {@link missingSourceRelation} already uses for postgres (and the same - * shape as `packages/types/src/driver-error-classification.ts` — #13279 + * shape as `packages/types/src/driver-error-classification.ts` — commit 6a180e42d * moved it there from `metadata/src/utils/schema-sync-errors.ts`), so "is * something missing" and "what is missing" can no longer disagree on this * limb. @@ -255,7 +255,7 @@ function isMissingColumnOfRelation(message: string): boolean { * exclude it — only asking the more specific question FIRST can. That makes the * ORDER the fix, not the pattern. * - * [#17130] EXPORTED — module-internal still (it is absent from `index.ts`, and + * [commit 54b3d1d4a] EXPORTED — module-internal still (it is absent from `index.ts`, and * this package's `exports` map publishes only that entry, so no consumer can * reach it), but reachable from `refusal-wording-collision.test.ts`. That guard * asserts no BARE refusal this package raises can be mistaken for a driver @@ -556,7 +556,7 @@ export interface AnalyticsServiceConfig { * forward this field or a measure-scoped filter `ObjectQLStrategy` * lowers never reaches storage. * - `method` is the spec's OWN six-value `AggregationFunction`, not - * `string`: #12776 narrowed the contract, #12940 brought this mirror + * `string`: #12776 narrowed the contract, commit aa16721b6 brought this mirror * back into line. Widening it here again would not be a local matter — * a bridge author types their handler against THIS declaration, so what * they would get is a vocabulary the contract no longer has. @@ -1507,7 +1507,7 @@ export class AnalyticsService implements IAnalyticsService { * Fail-closed: if the provider throws for an object, the whole query is * rejected rather than emitting SQL with that object unscoped. * - * [#17130] And the rejection DECLARES itself. This throw lands inside + * [commit 54b3d1d4a] And the rejection DECLARES itself. This throw lands inside * {@link AnalyticsService.queryDataset}'s catch, whose first question is * `hasDeclaredErrorEnvelope` and whose second is {@link isMissingSourceError} * — six substrings over driver phrasing, three of which are what a registry @@ -1535,7 +1535,7 @@ export class AnalyticsService implements IAnalyticsService { `rejecting query (fail-closed, ADR-0021 D-C)`, e instanceof Error ? e : new Error(String(e)), ); - // ⛔ The message is unchanged, deliberately: #17130's fix is the + // ⛔ The message is unchanged, deliberately: commit 54b3d1d4a's fix is the // DECLARATION, not a luckier string. Rewording to dodge the sniffer // would leave the next author to rediscover the mine. throw readScopeUnresolvedError( diff --git a/packages/services/service-analytics/src/dataset-executor.ts b/packages/services/service-analytics/src/dataset-executor.ts index f6e0d7e93de..68c9cb3dfde 100644 --- a/packages/services/service-analytics/src/dataset-executor.ts +++ b/packages/services/service-analytics/src/dataset-executor.ts @@ -595,7 +595,7 @@ function shiftYear(date: string, years: number): string { * ## What was wrong * * The STRING arm was spelled `[td.dateRange, td.dateRange]` — the degenerate - * `[range, range]` fallback #17015 removed from every OTHER analytics face. + * `[range, range]` fallback commit 0da638cd9 removed from every OTHER analytics face. * `parseUTC` was handed the preset NAME, so a DECLARED, honoured member of the * closed vocabulary was refused. MEASURED on `b3b43b6ea`, `last_30_days` plus * `compareTo`: @@ -606,7 +606,7 @@ function shiftYear(date: string, years: number): string { * * ⇒ the diagnostic is not merely unhelpful, it is FALSE, and it sends the * author to check a date that is exactly what the schema and the docs tell them - * to write. This face was not in #17015's kit, so nothing measured it. + * to write. This face was not in commit 0da638cd9's kit, so nothing measured it. * * ## Why it reports INSTANTS while `runCompare` shifts DAYS * @@ -619,7 +619,7 @@ function shiftYear(date: string, years: number): string { * ⛔ The ARRAY arm is the CALLER's explicit window and is handed back bound for * bound, with the inclusive upper reading it has always had (#16179) — the * refusal for anything that is not a two-bound window is - * `explicitDateRangeWindow`'s, unchanged (#17124). + * `explicitDateRangeWindow`'s, unchanged (commit 86c505286). * * @throws the ADR-0112 `400 ANALYTICS_DATE_RANGE_UNRECOGNIZED` envelope for a * string outside `DATE_RANGE_PRESETS` — the same envelope the sibling faces @@ -1506,7 +1506,7 @@ export class DatasetExecutor { // questions are answered in one place — see `resolveCompareDimension`. const dimension = resolveCompareDimension(selection); const td = (selection.timeDimensions ?? []).find((t) => t.dimension === dimension)!; - // [#17124] The ARRAY arm goes through the one `explicitDateRangeWindow` every + // [commit 86c505286] The ARRAY arm goes through the one `explicitDateRangeWindow` every // face in this package calls. ⛔ What this replaced filled a missing upper // bound in from the lower one, so a one-element array silently became a // point window HERE while the primary pass it is compared against may have @@ -1515,7 +1515,7 @@ export class DatasetExecutor { // [#17973] The STRING arm is the CLOSED preset vocabulary, lowered by the one // shared `resolveAnalyticsDateRangeString` every other face calls and then // projected onto calendar days. ⛔ What this replaced was the - // degenerate `[range, range]` fallback #17015 removed everywhere else: it + // degenerate `[range, range]` fallback commit 0da638cd9 removed everywhere else: it // handed the preset NAME to this module's date parser, so `last_30_days` — // declared, honoured, and exactly what the schema tells an author to write — // came back as `DATASET_INVALID "invalid date in dateRange"`. A false @@ -1538,7 +1538,7 @@ export class DatasetExecutor { ); const range: [string, string] = Array.isArray(td.dateRange) ? // The caller's own bounds, untouched — `explicitDateRangeWindow` already - // refused anything that is not a two-bound window (#17124). + // refused anything that is not a two-bound window (commit 86c505286). [lowered.start, lowered.end] : inclusiveCalendarDayWindow(lowered, timezone); const shifted = shiftRange(range, cmp.kind); diff --git a/packages/services/service-analytics/src/date-range-array-arm.ts b/packages/services/service-analytics/src/date-range-array-arm.ts index cb534637d9a..1000d07cb6a 100644 --- a/packages/services/service-analytics/src/date-range-array-arm.ts +++ b/packages/services/service-analytics/src/date-range-array-arm.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#17124] THE one reading of `dateRange`'s ARRAY arm, for every face in this + * [commit 86c505286] THE one reading of `dateRange`'s ARRAY arm, for every face in this * package. * * ## What was wrong diff --git a/packages/services/service-analytics/src/measure-result-type.ts b/packages/services/service-analytics/src/measure-result-type.ts index c0af2790a83..1d18c7768ea 100644 --- a/packages/services/service-analytics/src/measure-result-type.ts +++ b/packages/services/service-analytics/src/measure-result-type.ts @@ -112,8 +112,8 @@ import { isAggregateCompatibleWithFieldType } from '@objectstack/spec/data'; * `number` it has (the accurate word for the raw SQLite value). * * ⚠️ ⛔ This is NOT a missing refusal. `AGGREGATE_FIELD_TYPE_COMPATIBILITY` - * ACCEPTS `sum` / `avg` / `min` / `max` over `boolean` / `toggle` — #16685 - * ruled A, landed as #16750, on the authority of maintainer ruling #11152 — so + * ACCEPTS `sum` / `avg` / `min` / `max` over `boolean` / `toggle` — commit + * ed7243d52 (#16750) added those rows, on the authority of maintainer ruling #11152 — so * the pair is deliberately allowed and the compile leg never refuses it. This * is the one accepted class where the rule declines, and it declines because * the readings disagree, not because the pair is unsupported. diff --git a/packages/services/service-analytics/src/plugin.ts b/packages/services/service-analytics/src/plugin.ts index f1c12b7d761..ad3dc9fff4d 100644 --- a/packages/services/service-analytics/src/plugin.ts +++ b/packages/services/service-analytics/src/plugin.ts @@ -33,7 +33,7 @@ import { asAcceptedSqlDialect, type AcceptedSqlDialect } from './text-match-sql. * `string` where the contract declares a six-value enum, and nothing compiled * the two against each other. * - * Three of these members could not be named from a contract until #12248 + * Three of these members could not be named from a contract until commit 8425c17cc * landed the #11833 ruling: `resolveEffectiveDatasource` and * `getDriverForObject` (fork 1, declared OPTIONAL exactly so seams like this * one keep degrading), and `getObject`'s structured `ServiceObject` return @@ -66,7 +66,7 @@ type DataEngineLike = * `temporalFilterValue` / `temporalFilterColumnSql` are first-class contract * members since ADR-0053 D-A2, no longer a duck-typed local invention. * - * Since #12248 declared `IDataEngine.getDriverForObject?`, this is the + * Since commit 8425c17cc declared `IDataEngine.getDriverForObject?`, this is the * RETURN-side narrowing that member's own docblock prescribes, applied at the * two call sites below — not a re-declaration of the member. `Pick` admits the full contract value, so the engine keeps handing back whatever @@ -104,7 +104,7 @@ type DialectNamingDriver = { readonly dialectName?: unknown }; * Both sides now declare the same six-value enum: `IDataEngine.aggregate`'s * `aggregations[].function`, and — since #12776 — the analytics strategy * contract (`StrategyContext.executeAggregate`) plus the two consumer-local - * config mirrors this package keeps in lockstep with it (#12940). So this + * config mirrors this package keeps in lockstep with it (commit aa16721b6). So this * parse is DEFENCE IN DEPTH behind a compile-time check, not the only check * (#11833). * @@ -113,7 +113,7 @@ type DialectNamingDriver = { readonly dialectName?: unknown }; * through a cube object that never met `CubeSchema`'s parse (the path * `aggregate-bridge-function-vocabulary.test.ts` drives end to end), still * reaches this seam carrying a method the engine does not declare. What the - * refusal buys is in `plugin.ts`'s forward below and in #12209: the engine is + * refusal buys is in `plugin.ts`'s forward below and in commit 017130a09: the engine is * never handed a `function` no driver declares. * * Parsing with the spec's OWN enum keeps a single vocabulary — no local @@ -170,7 +170,7 @@ export interface AnalyticsServicePluginOptions { * measure-scoped filter this plugin lowers onto the aggregation * silently never reaches storage. * - `method` is the spec's OWN six-value `AggregationFunction`, not - * `string`: #12776 narrowed the contract, #12940 brought this mirror + * `string`: #12776 narrowed the contract, commit aa16721b6 brought this mirror * back into line. This is the declaration a custom-bridge author types * their handler against, so it is where the compile-time vocabulary * #12776 bought for strategy authors reaches them too. @@ -371,7 +371,7 @@ export class AnalyticsServicePlugin implements Plugin { // correct signal, and the one the deleted structural type hid by // declaring `function: string` on both sides. // - // Since #12776 (contract) and #12940 (this plugin's own config + // Since #12776 (contract) and commit aa16721b6 (this plugin's own config // mirror above), BOTH ends declare the enum, so the rename is // enum-to-enum and the parse below is defence in depth behind a // compile-time check rather than the only check — see @@ -389,7 +389,7 @@ export class AnalyticsServicePlugin implements Plugin { // method — a custom-SQL measure (`AggregationMetricType` // `number`/`string`/`boolean`) — is already refused upstream with a // caller-blaming 400 by `ObjectQLStrategy.resolveMeasureAggregation` - // (#12209). Anything still arriving here is host drift, which that + // (commit 017130a09). Anything still arriving here is host drift, which that // refusal's docblock assigns to the undeclared-500 tier — so this // throws rather than re-blaming the caller, and it answers loudly // instead of letting the engine answer `null` per bucket under the @@ -557,7 +557,7 @@ export class AnalyticsServicePlugin implements Plugin { * has since ADR-0021 D-C), so serving nothing is the same outcome the * object-level bridge produces. * - * [#17130] It needs no NEW error code — and the clause that used to follow, + * [commit 54b3d1d4a] It needs no NEW error code — and the clause that used to follow, * "and no new envelope here", was the finding. An envelope is not a second * outcome, it is what stops the outcome being decided by wording: * `queryDataset`'s catch re-throws whatever declares `code` + `status` and @@ -629,7 +629,7 @@ export class AnalyticsServicePlugin implements Plugin { 'A security service is wired on this deployment, so analytics must not fall ' + 'open and serve rows with no row-level policy applied.', ); - // [#17130] Declared, not bare. `resolveReadScopes` replaces this + // [commit 54b3d1d4a] Declared, not bare. `resolveReadScopes` replaces this // error with its own on the dataset path, but this provider is read // by four consumers and a bare refusal is the one kind // `queryDataset`'s catch classifies by WORDING — three of the six diff --git a/packages/services/service-analytics/src/preview-evaluator.ts b/packages/services/service-analytics/src/preview-evaluator.ts index 7980b2044f1..b5949b8e98c 100644 --- a/packages/services/service-analytics/src/preview-evaluator.ts +++ b/packages/services/service-analytics/src/preview-evaluator.ts @@ -622,7 +622,7 @@ export function lowerPreviewDateRange( const window = resolveAnalyticsDateRangeString(dateRange as string, { timezone }); return { start: window.start, end: window.end, endExclusive: window.endExclusive }; } - // [#17124] An oddly-sized array is REFUSED, by the one + // [commit 86c505286] An oddly-sized array is REFUSED, by the one // `explicitDateRangeWindow` every face in this package calls. ⛔ What this // replaced left the upper bound UNWRITTEN — `String(undefined)` is // `"undefined"`, and every ISO date sorts below it, so a one-entry array diff --git a/packages/services/service-analytics/src/read-scope-refusal.ts b/packages/services/service-analytics/src/read-scope-refusal.ts index 04b52028caa..1571f60c1b9 100644 --- a/packages/services/service-analytics/src/read-scope-refusal.ts +++ b/packages/services/service-analytics/src/read-scope-refusal.ts @@ -1,7 +1,7 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. /** - * [#17130] The row-scope fail-closed refusals, in the ADR-0112 envelope. + * [commit 54b3d1d4a] The row-scope fail-closed refusals, in the ADR-0112 envelope. * * ## What was wrong * @@ -26,7 +26,7 @@ * the caller as a confident empty chart, with one `warn` and no exception. * * The two messages happen to match none of the six today. ⛔ That is a - * coincidence, not a construction, and #17130 exists to remove it rather than + * coincidence, not a construction, and commit 54b3d1d4a was made to remove it rather than * to keep picking lucky strings. Declaring the envelope answers the * classification question at the producer, where it is known, so no reword of * these messages — and no message the resolution stage grows later — can ever diff --git a/packages/services/service-analytics/src/read-scope-sql.ts b/packages/services/service-analytics/src/read-scope-sql.ts index 630136228c6..425cb8e1a52 100644 --- a/packages/services/service-analytics/src/read-scope-sql.ts +++ b/packages/services/service-analytics/src/read-scope-sql.ts @@ -179,7 +179,7 @@ import { * anyone parsing prose. * * ⚠️ At `error.code` — NOT `error.details.code`, which is where this note - * pointed until #6123 corrected it. `errorResponseBase` only STAGES the code in + * pointed until commit 59d1933f9 corrected it. `errorResponseBase` only STAGES the code in * a `details` object; `buildApiError` then runs `splitSemanticCode` * (`@objectstack/runtime`, `src/error-envelope.ts:117`), which PROMOTES it into * the declared `ApiErrorSchema` field and returns the now-empty `details` as diff --git a/packages/services/service-analytics/src/strategies/native-sql-strategy.ts b/packages/services/service-analytics/src/strategies/native-sql-strategy.ts index 16607fbe9f5..333054d5cbb 100644 --- a/packages/services/service-analytics/src/strategies/native-sql-strategy.ts +++ b/packages/services/service-analytics/src/strategies/native-sql-strategy.ts @@ -108,7 +108,7 @@ export const CONDITIONAL_AGGREGATE_SQL_KEYS = Object.keys(CONDITIONAL_AGGREGATE_ * two sets partition `AggregationMetricType`, so a new member fails a test * instead of picking a default. * - * [#12209] `ObjectQLStrategy.resolveMeasureAggregation` keys its refusal arm on + * [commit 017130a09] `ObjectQLStrategy.resolveMeasureAggregation` keys its refusal arm on * this same set — the engine aggregate AST cannot carry a raw SQL expression, * so the ObjectQL path REFUSES exactly what this strategy emits verbatim. One * set, two strategies, so the partition cannot fork per path. @@ -513,7 +513,7 @@ export class NativeSQLStrategy implements AnalyticsStrategy { : resolveAnalyticsDateRangeString(td.dateRange, { timezone: query.timezone }); const range = resolved ? ([resolved.start, resolved.end] as [string, string]) - // [#17124] An oddly-sized array is REFUSED, by the one + // [commit 86c505286] An oddly-sized array is REFUSED, by the one // `explicitDateRangeWindow` every face in this package calls. ⛔ What // this replaced was a silent `if (range.length === 2)` DROP: a // one-element array emitted no time clause at all, so the query read diff --git a/packages/services/service-analytics/src/strategies/objectql-strategy.ts b/packages/services/service-analytics/src/strategies/objectql-strategy.ts index f71f818c557..a400976a40c 100644 --- a/packages/services/service-analytics/src/strategies/objectql-strategy.ts +++ b/packages/services/service-analytics/src/strategies/objectql-strategy.ts @@ -37,13 +37,13 @@ import { type MeasureRecombine, type RecombinableMethod, } from './cross-object-rebucket.js'; -// [#12209] The custom-SQL half of the `AggregationMetricType` partition, ONE +// [commit 017130a09] The custom-SQL half of the `AggregationMetricType` partition, ONE // source shared with `NativeSQLStrategy` and pinned against the spec enum by // `metric-type-coverage.test.ts` — a second literal set here would drift. import { EXPRESSION_METRIC_TYPES } from './native-sql-strategy.js'; /** - * [#10861 / #11461] Where a member in the cross-object envelope's inventory + * [#10861 / commit 399ecad58] Where a member in the cross-object envelope's inventory * came from. * * THREE producers put predicates in front of `engine.aggregate` on this path: @@ -726,7 +726,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * the members inside were unreadable from the outside and the envelope check * could not reject what it could not see. * - * ## Three producers, one inventory (#10861, #11461) + * ## Three producers, one inventory (#10861, commit 399ecad58) * * The caller's `where` is not the only thing that reaches `engine.aggregate` * as a predicate. Since PR #10758 the compiled dataset's own definition-level @@ -741,7 +741,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * which driver will serve the dataset and would refuse a dataset that is * perfectly legal on a native-SQL deployment. * - * [#11461] #10413 phase 2 then added a THIRD producer with the same reach and + * [commit 399ecad58] #10413 phase 2 then added a THIRD producer with the same reach and * none of the coverage: a compiled measure's own `filter`, lowered onto that * measure's `aggregations[].filter` entry (#10576). This view enumerated two * origins, so the third was invisible to the envelope check and the arm of @@ -784,7 +784,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * last write wins on a duplicate key. Two things follow, in that order of * importance. A member named by the request too keeps the CALLER's provenance, * because if it is in the request that is the actionable place to fix it. And - * every shape that was refused before #11461 keeps the exact message it had: + * every shape refused before commit 399ecad58 keeps the exact message it had: * the new origin can only ever win a key no older producer names. * * Time-dimension WINDOWS are deliberately absent (they live in @@ -801,14 +801,14 @@ export class ObjectQLStrategy implements AnalyticsStrategy { ): Record { // Read from the SAME channel both doors lower the scope from, so the view // and the predicate cannot disagree about what the engine will receive. - // [#11461] The whole scope now, not just `.filter` — the per-measure filters + // [commit 399ecad58] The whole scope now, not just `.filter` — the per-measure filters // travel the identical channel to the identical engine call. const datasetScope = (ctx as DatasetScopedStrategyContext).getDatasetScope?.(query.cube!); const leaves = (node: ReturnType, origin: FilterMemberOrigin) => collectFilterLeaves(node).map( (f) => [this.resolveFieldName(cube, f.member, 'any'), origin] as const, ); - // [#11461] Keyed by measure so the refusal can name the measure to go and + // [commit 399ecad58] Keyed by measure so the refusal can name the measure to go and // edit; `query.measures` is the iteration order both aggregation loops use, // so the view covers exactly the filters that will be lowered. const measureLeaves = (query.measures ?? []).flatMap((m) => { @@ -842,7 +842,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * (needs a real join to evaluate), a cross-object leaf in the DATASET's own * definition-level `filter` (#10861 — same join it does not have, arriving * from the producer PR #10758 added), a cross-object leaf in ONE MEASURE's own - * `filter` (#11461 — the same join again, arriving from the producer #10413 + * `filter` (commit 399ecad58 — the same join again, arriving from the producer #10413 * phase 2 added), a MULTI-HOP dimension (`a.b.c`), or a non-recombinable * measure (`avg`/`count_distinct`, whose sub-bucket values cannot be merged). * A loud error beats the silent mis-bucket #3654 kills. @@ -854,7 +854,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * [#5716] All six refusals below are `invalidMemberError` — `INVALID_FIELD` / * 400, naming the member — and the four that predate #10861 keep their * MESSAGES unchanged (they are good diagnostics, and #5923's tests read - * them); so does #10861's own, which #11461 left untouched beside it. Each is decided by two facts and nothing else: a member that will + * them); so does #10861's own, which commit 399ecad58 left untouched beside it. Each is decided by two facts and nothing else: a member that will * reach the engine's predicate, and whether that member resolves across a * join. Neither is an internal invariant — a cube where the member exists and * a driver that could serve it are both perfectly ordinary, which is exactly @@ -863,7 +863,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * because the fix is always to change or drop ONE named member, and because * four of them fire on `/analytics/query` where no dataset exists. * - * [#10861, #11461] The fifth and sixth are the exceptions that prove the rule + * [#10861, commit 399ecad58] The fifth and sixth are the exceptions that prove the rule * and are written to it: they can only fire where a dataset DOES exist, and * they are the two refusals here whose member no request key named — so each * carries `cube` and no `param`, and says in its own words which document to @@ -962,7 +962,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { ); } - // [#11461] The THIRD producer, and the same physical verdict a third time: + // [commit 399ecad58] The THIRD producer, and the same physical verdict a third time: // a leaf of one compiled MEASURE's own `filter`, lowered onto that measure's // `aggregations[].filter` entry (#10413 phase 2 / #10576). Checked last, so // every shape refused before this card is refused with the message it @@ -1460,7 +1460,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { | { sql: string; type: string } | undefined; if (direct) { - // [#12209] A custom-SQL measure (`AggregationMetricType` + // [commit 017130a09] A custom-SQL measure (`AggregationMetricType` // `number`/`string`/`boolean`) is REFUSED here rather than forwarded. Its // `sql` IS the whole computation (a ratio, a `CASE`, a window function), // and the engine aggregate AST has no place to carry a raw SQL @@ -1777,7 +1777,7 @@ export class ObjectQLStrategy implements AnalyticsStrategy { * a vocabulary word against a timestamp — which is precisely how the two * backends came to answer one bad input with opposite wrong answers. * - * [#17124] An oddly-sized array is REFUSED with the same envelope, by the one + * [commit 86c505286] An oddly-sized array is REFUSED with the same envelope, by the one * `explicitDateRangeWindow` every face in this package now calls. ⛔ The * per-face fallback this replaced — take the first two entries, a one-entry * array degenerating to a point — was one of THREE readings of the same From 82d2b40b21bc9ac0dc6d3c3af3d6dd04537c53ed Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 22:05:18 +0000 Subject: [PATCH 2/2] chore(changeset): patch for the service-analytics provenance re-anchoring The rewritten docblocks and inline comments reach dist/ (measured on the built package), so the change ships bytes and takes a patch changeset. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude --- .../20596-service-analytics-provenance-anchors.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/20596-service-analytics-provenance-anchors.md diff --git a/.changeset/20596-service-analytics-provenance-anchors.md b/.changeset/20596-service-analytics-provenance-anchors.md new file mode 100644 index 00000000000..7002507ab29 --- /dev/null +++ b/.changeset/20596-service-analytics-provenance-anchors.md @@ -0,0 +1,10 @@ +--- +'@objectstack/service-analytics': patch +--- + +Provenance comments in `service-analytics` were re-anchored + +Comment and docblock lines under `src/` that cited tracker numbers which no +longer resolve on GitHub now cite the commit in this repository's history that +decided the matter, and say in their own words what was decided. Comments +only: no type, schema, export, log or refusal text, or runtime behaviour changes.