Skip to content

Commit 518d5e5

Browse files
os-trumpclaude
andauthored
fix(scripts,spec): doc-authoring Rule 3 reaches refusal prose built inside functions, and the 55 tracker ids it was blind to (#13151)
* feat(gate): check-doc-authoring Rule 3 reaches refusal prose built inside functions Narrow widening only: the climb crosses a function boundary when the FUNCTION ITSELF sits in a recognised customer-facing position -- a zod `error` option, a text-sink const, or a `: StrictObjectOptions` factory. Never an unconditional climb through function bodies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 * wip: strip tracker ids from the fourth refusal population * wip: move test twins + regenerate reference docs * wip: changeset * test(service-analytics): re-pin the array_agg retirement twin on the ADR, not the tracker id The consumer-side twin of the packages/spec strip: this regex spelled #6188, which the refusal prose no longer carries. Re-pinned on the version + ADR the prescription keeps, plus the 'Delete the aggregation' clause, plus a negative pin that no tracker id is present. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LpRNHxWZgSUgVnFT9mQQo4 --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5a9b7a0 commit 518d5e5

31 files changed

Lines changed: 636 additions & 251 deletions
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
Strip internal issue-tracker ids from refusal prose built inside functions
6+
7+
The fourth customer-facing refusal population in `packages/spec`: prose a
8+
FUNCTION returns rather than prose written at a recognised position — a zod
9+
`error: (issue) => …` map, a hoisted `const X = (key) => '…'` message builder
10+
referenced from `message:` or `retiredKey(X(…))`, a `$ZodErrorMap` const the
11+
`error` callback dispatches to, a `(v): StrictObjectOptions => ({ history })`
12+
options factory. It reaches exactly the same reader at exactly the same moment
13+
as the three populations already stripped: the author whose metadata was just
14+
rejected. That reader has no tracker, so `#NNNN` was a citation-shaped token
15+
resolving to nothing in the one sentence that most needs to be actionable.
16+
17+
47 literals carrying 55 tracker ids across 13 sources.
18+
19+
| how the prose reaches the author | literals | ids | files |
20+
|---|---:|---:|---:|
21+
| built INSIDE a function in a recognised position | 28 | 32 | 8 |
22+
| hoisted into a const an `error:` callback dispatches to | 19 | 23 | 8 |
23+
24+
**Kept, deliberately:** ADR ids, protocol and package versions, error codes
25+
(`400 INVALID_FIELD` traces the runtime twin far better than the id beside it)
26+
and the `os migrate meta --from <N>` commands — the anchors a customer can
27+
actually resolve. Where an id was the whole parenthetical, the parenthetical
28+
went with it; where it was load-bearing for an internal reader, it moved to an
29+
adjacent `//` comment.
30+
31+
`check-doc-authoring` Rule 3 could not see this population at all: its climb
32+
returned `undefined` at `ArrowFunction` / `ReturnStatement`, so the gate printed
33+
`0 violations` over four populated buckets while a fifth sat outside every one
34+
of them. The rule now crosses a function boundary — but only when the FUNCTION
35+
ITSELF sits in a recognised customer-facing position, never unconditionally
36+
through arbitrary function bodies, which would report values as prose. The new
37+
population is its own `functionBuilt` bucket so it carries its own blindness
38+
floor, since an unrecognised spelling produces no flag silently.

content/docs/references/data/driver-nosql.mdx

Lines changed: 30 additions & 30 deletions
Large diffs are not rendered by default.

content/docs/references/data/driver-sql.mdx

Lines changed: 30 additions & 30 deletions
Large diffs are not rendered by default.

content/docs/references/data/driver.mdx

Lines changed: 60 additions & 60 deletions
Large diffs are not rendered by default.

content/docs/references/ui/component.mdx

Lines changed: 12 additions & 12 deletions
Large diffs are not rendered by default.

content/docs/references/ui/dashboard.mdx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ const result = DashboardSchema.parse(data);
7070
| **colorVariant** | `Enum<'default' \| 'blue' \| 'teal' \| 'orange' \| 'purple' \| 'success' \| 'warning' \| 'danger'>` | optional | Widget color variant for theming |
7171
| **requiresObject** | `string` | optional | Hide the widget unless the named object is registered |
7272
| **requiresService** | `string` | optional | Hide the widget unless the named kernel service is registered |
73-
| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
74-
| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
75-
| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
73+
| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
74+
| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
75+
| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
7676
| **filter** | `any` | optional | Presentation-scope filter (runtimeFilter) |
7777
| **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) |
7878
| **dataset** | `string` || Dataset name to bind (ADR-0021) |
@@ -175,9 +175,9 @@ Dashboard header action
175175
| **colorVariant** | `Enum<'default' \| 'blue' \| 'teal' \| 'orange' \| 'purple' \| 'success' \| 'warning' \| 'danger'>` | optional | Widget color variant for theming |
176176
| **requiresObject** | `string` | optional | Hide the widget unless the named object is registered |
177177
| **requiresService** | `string` | optional | Hide the widget unless the named kernel service is registered |
178-
| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
179-
| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
180-
| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (#5010, ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
178+
| **actionUrl** | `never` | optional | [REMOVED] `dashboard.widgets[].actionUrl` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
179+
| **actionType** | `never` | optional | [REMOVED] `dashboard.widgets[].actionType` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
180+
| **actionIcon** | `never` | optional | [REMOVED] `dashboard.widgets[].actionIcon` was removed in @objectstack/spec 17.0.0 (ADR-0049 enforce-or-remove) — a dashboard widget has NO action button, and never had one. No renderer draws per-widget chrome for it: every action the dashboard dispatches comes from `header.actions[]`. The three keys `actionUrl` / `actionType` / `actionIcon` went together; delete all three. Put the affordance on the dashboard header instead — `header: { actions: [{ label, actionUrl, actionType, icon }] }` — which IS dispatched (`DashboardHeaderAction`, same vocabulary, and `icon` is the header spelling of `actionIcon`). For a per-ROW affordance, the widget to reach for is a `table`/`pivot` bound to a dataset: its rows are clickable and drill through the semantic layer. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
181181
| **filter** | `any` | optional | Presentation-scope filter (runtimeFilter) |
182182
| **compareTo** | `{ kind: Enum<'previousPeriod' \| 'previousYear'>; dimension?: string }` | optional | Period-over-period comparison window (`{ kind, dimension? }`) |
183183
| **dataset** | `string` || Dataset name to bind (ADR-0021) |

packages/services/service-analytics/src/__tests__/dataset-compiler.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,27 @@ describe('compileDataset', () => {
105105
// carries a prescription instead of naming the supported list. Re-pointed
106106
// rather than deleted: the dataset measure is one of the retirement's two
107107
// authoring surfaces, and this is where that surface is exercised.
108+
//
109+
// ⚠️ Pinned on the CUSTOMER-RESOLVABLE anchors the prescription carries —
110+
// the package version and the ADR — never on a tracker id. The id used to be
111+
// in this message and is not any more: `check:doc-authoring` Rule 3 bans an
112+
// internal `#NNNN` from prose printed at a refused author, and a consumer-side
113+
// regex spelling one is a pin on the thing that must not be there.
108114
it('rejects a retired aggregate at the schema, with the retirement prescription', () => {
109-
expect(() => DatasetSchema.parse({
115+
const parse = () => DatasetSchema.parse({
110116
name: 'agg',
111117
label: 'Agg',
112118
object: 'opportunity',
113119
dimensions: [],
114120
measures: [{ name: 'tags', aggregate: 'array_agg', field: 'tag' }],
115-
})).toThrowError(/`array_agg`.*was removed.*#6188/s);
121+
});
122+
expect(parse).toThrowError(/`array_agg`.*was removed.*ADR-0049 enforce-or-remove/s);
123+
// It is a PRESCRIPTION, not a bare refusal: it must still say what to do.
124+
expect(parse).toThrowError(/Delete the aggregation/s);
125+
// ...and it must carry no tracker id, in either the bare or repo-qualified
126+
// spelling. Negative pin, so a re-introduced id reds HERE too and not only
127+
// at the gate.
128+
expect(parse).not.toThrowError(/#\d{3,5}(?![0-9A-Za-z])/s);
116129
});
117130

118131
it('still compiles the aggregate the ruling kept', () => {

packages/spec/src/data/driver.zod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const DriverOptionsSchema = lazySchema(() => z.object({
111111
* record is the semantic migration `driver-capabilities-inert-bits-removed`.
112112
*/
113113
const capRemoved = (key: string, mechanism: string) =>
114-
`\`DriverCapabilities.${key}\` was removed in @objectstack/spec 17.0.0 (#4634, ADR-0049 ` +
114+
`\`DriverCapabilities.${key}\` was removed in @objectstack/spec 17.0.0 (ADR-0049 ` +
115115
`enforce-or-remove) — no code in any repository ever read it, so its value never changed ` +
116116
`which code path ran. ${mechanism} Delete the key.`;
117117

0 commit comments

Comments
 (0)