Skip to content

Commit 94763d7

Browse files
fix(spec): activityMilestones[].type describe states the real default (#20494) (#20521)
Fixes #20494 Clause-②: no ## What changed - `packages/spec/src/data/object.zod.ts` (~:2123): rewrote the `activityMilestones[].type` `.describe()` to state the real default — an unset `type` keeps the update row's kind, `updated`. No schema shape change. - Regenerated `content/docs/references/data/object.mdx` with `gen:docs` (never hand-edited). - `packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts`: corrected the pin's title and docblock only — they stop describing a divergence and stop saying it was "filed separately" (this card is where it was filed). Its assertions are byte-for-byte unchanged. - `.changeset/20494-milestone-type-default-describe.md` (`@objectstack/spec` patch, `Clause-②: no`). ## Why `audit-writers.ts` starts `activityType` from `activityTypeFor(action)`, and a milestone can only fire on the UPDATE branch (`create` / `delete` return their own summary before the milestone match runs), so an unset `type` has always emitted `updated`, never `completed`. `milestone.type` overrides it only when the author actually sets it — that half of the old describe was correct and is unchanged. The plugin-audit pin (`activity-type-vocabulary-enforcement.test.ts`) already measured this real answer; only its title and docblock claimed a divergence and said the finding was filed separately. ## Dedupe search for other stale copies A repo-wide grep for the old wording, and for every `activityMilestones` mention, found no other hand-written copy of the "completed" default. `object.form.ts`'s `activityMilestones.type` help text (shipped with PR #20485 — "Unset: updated.") already states the real default and is unchanged. ## Verification record Commands run in this worktree, foreground, through `scripts/pm/os-verify-lock.sh` where heavy: - `pnpm --filter @objectstack/spec build` — pass. - `pnpm --filter @objectstack/spec run check:generated` — all 15 generated artifacts green after `pnpm --filter @objectstack/spec run gen:docs` (only `check:docs` was stale, for exactly this describe change). - `pnpm --filter @objectstack/spec test` — 572 test files / 16796 tests passed, 1 todo. - `pnpm --filter @objectstack/spec typecheck` — pass. - `pnpm --filter '@objectstack/plugin-audit^...' build` (dependency closure) — pass. - `pnpm --filter @objectstack/plugin-audit test` — 25 test files / 363 tests passed, including the renamed pin. - `pnpm --filter @objectstack/plugin-audit typecheck` — pass. - `pnpm check:doc-authoring` (dispatch-named — no tracker number in the describe) — pass. - `node scripts/check-nul-bytes.mjs` — pass. - `node scripts/check-adr-0087-registration.mjs`, `check-changeset-no-major.mjs`, `check-empty-changeset.mjs`, `check-issue-citations.mjs` (`--self-test` and `--base origin/main`, matched by the new changeset file) — all pass. `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran <record>` reconciliation: 112 families derived from this diff's paths, 102 UNRUN locally. Those are whole-tree/whole-package scans this describe-text-only change (no export, no authorable key, no schema shape change) does not plausibly touch — `check:generated`'s own 15-gate sweep already covers the ones that read the schema/docs pipeline this change lands in (`check:docs`, `check:api-surface`, `check:authorable-surface`, `check:export-origins`, `check:declaration-map`, `check:strictness-ledger`, `check:liveness`, `check:test-typecheck`). The remaining UNRUN families are deferred to CI's farm per this repo's local-verification scope. ## Acceptance notes Nothing found outside this card's file surface. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent e1b736e commit 94763d7

4 files changed

Lines changed: 21 additions & 10 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`activityMilestones[].type`'s `.describe()` now states the real default: an unset `type` keeps the update row's kind, `updated` (#20494)
6+
7+
Clause-②: no
8+
9+
No behaviour changes and no schema shape change. `object.zod.ts`'s `activityMilestones[].type` field described its default as `"completed"`; the runtime never wrote that. `audit-writers.ts` starts `activityType` from `activityTypeFor(action)`, and a milestone can only fire on the UPDATE branch (`create` / `delete` return their own summary before the milestone match ever runs), so an unset `type` has always emitted `updated`. `milestone.type` overrides it only when the author actually sets it — that half of the describe was correct and is unchanged.
10+
11+
The corrected string is the published half: it ships in `packages/spec/dist/*.d.ts`, in the JSON Schema under `packages/spec/json-schema/`, and in the generated `content/docs/references/data/object.mdx` (regenerated with `gen:docs`, never hand-edited). A repo-wide search for the old wording found no other hand-written copy; `object.form.ts`'s `activityMilestones.type` help text ("Unset: updated.", shipped with PR #20485) already stated the real default and is unchanged.
12+
13+
`packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts` already measured the runtime's real answer — its title and docblock are corrected in the same PR to stop describing a divergence and stop saying the finding was "filed separately" (this card, #20494, is where it was filed). Its assertions are byte-for-byte unchanged.

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ const result = ApiMethod.parse(data);
354354
| **field** | `string` | ✅ | Field to watch (typically a status/stage select). |
355355
| **value** | `string` | ✅ | The value the field must transition INTO to fire the milestone. |
356356
| **summary** | `string` | ✅ | Activity summary template; `{field}` tokens interpolate the record value. e.g. "Deal won: `{name}`". |
357-
| **type** | `string` | optional | Activity type for the emitted row (default "completed"). |
357+
| **type** | `string` | optional | Activity type for the emitted row — left unset, it keeps the update row's kind, "updated" (a milestone only fires on an update). |
358358

359359
### Nested Shape: `Object.listViews[string]`
360360

‎packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,15 +266,13 @@ describe('[#8203] sys_activity.type — the writers emit declared values', () =>
266266
});
267267

268268
/**
269-
* The real default when a milestone omits `type`. Pinned because the spec's
270-
* own field description says otherwise — `object.zod.ts` documents
271-
* `activityMilestones[].type` as 'Activity type for the emitted row (default
272-
* "completed")', while the code default is `activityTypeFor(action)` and a
273-
* milestone can only fire on the UPDATE branch, making it `updated`.
274-
* Filed separately; pinned here so the divergence is measured rather than
275-
* argued from either side's prose.
269+
* The real default when a milestone omits `type`: `activityTypeFor(action)`,
270+
* and a milestone can only fire on the UPDATE branch, so the emitted row is
271+
* `updated`. `object.zod.ts`'s `activityMilestones[].type` describe states
272+
* this default (#20494); pinned here so the runtime behaviour stays
273+
* measured, not just described.
276274
*/
277-
it('a milestone without `type` emits `updated` — not the "completed" the spec text claims', async () => {
275+
it('a milestone without `type` keeps the update row\'s kind, `updated`', async () => {
278276
const { engine, storeFor } = await boot();
279277
await engine.insert('biz_ticket', { id: 't3', title: 'Three', stage: 'open' });
280278
await moveStage(engine, 't3', 'plain');

‎packages/spec/src/data/object.zod.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2120,7 +2120,7 @@ const ObjectSchemaBase = strictObject(
21202120
field: z.string().describe('Field to watch (typically a status/stage select).').meta({ title: 'Field' }),
21212121
value: z.string().describe('The value the field must transition INTO to fire the milestone.').meta({ title: 'Value' }),
21222122
summary: z.string().describe('Activity summary template; {field} tokens interpolate the record value. e.g. "Deal won: {name}".').meta({ title: 'Summary' }),
2123-
type: z.string().optional().describe('Activity type for the emitted row (default "completed").').meta({ title: 'Type' }),
2123+
type: z.string().optional().describe('Activity type for the emitted row — left unset, it keeps the update row\'s kind, "updated" (a milestone only fires on an update).').meta({ title: 'Type' }),
21242124
})).optional().describe('Declarative semantic activity milestones — emit a templated timeline row when a field transitions into a value, no hook code (ADR-0052 §5b.2).'),
21252125

21262126
// ADR-0020: record state machines are not a separate `stateMachines` map —

0 commit comments

Comments
 (0)