Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/20596-service-analytics-provenance-anchors.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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"
*
Expand Down Expand Up @@ -84,7 +84,7 @@ const FIELD_TYPES: Record<string, string> = {
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',
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ async function lowerViaPreview(range: string | readonly string[]): Promise<Lower

/**
* [#17973] The dataset executor's `compareTo` window — the FOURTH face in this
* package, and the one #17015 never reached. It kept the degenerate
* package, and the one commit 0da638cd9 never reached. It kept the degenerate
* `[range, range]` fallback every sibling shed, so a DECLARED preset plus
* `compareTo` was refused outright. MEASURED on `b3b43b6ea`, before the fix:
*
Expand Down Expand Up @@ -391,7 +391,7 @@ describe('#17973 — the dataset executor APPLIES the window it reports', () =>
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']],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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';
Expand All @@ -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<string, unknown> }
/**
* [#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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
// ────────────────────────────────────────────────────────────────────────────

/**
Expand Down Expand Up @@ -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', {
Expand All @@ -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' },
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
Expand All @@ -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.
*
Expand Down Expand Up @@ -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).');
});

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
*
Expand All @@ -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
Expand All @@ -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
*
Expand Down
Loading
Loading