diff --git a/.changeset/20494-milestone-type-default-describe.md b/.changeset/20494-milestone-type-default-describe.md new file mode 100644 index 00000000000..3eb6d4b3f7c --- /dev/null +++ b/.changeset/20494-milestone-type-default-describe.md @@ -0,0 +1,13 @@ +--- +'@objectstack/spec': patch +--- + +`activityMilestones[].type`'s `.describe()` now states the real default: an unset `type` keeps the update row's kind, `updated` (#20494) + +Clause-②: no + +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. + +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. + +`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. diff --git a/content/docs/references/data/object.mdx b/content/docs/references/data/object.mdx index 336d0abcc39..8c7f36be7ce 100644 --- a/content/docs/references/data/object.mdx +++ b/content/docs/references/data/object.mdx @@ -354,7 +354,7 @@ const result = ApiMethod.parse(data); | **field** | `string` | ✅ | Field to watch (typically a status/stage select). | | **value** | `string` | ✅ | The value the field must transition INTO to fire the milestone. | | **summary** | `string` | ✅ | Activity summary template; `{field}` tokens interpolate the record value. e.g. "Deal won: `{name}`". | -| **type** | `string` | optional | Activity type for the emitted row (default "completed"). | +| **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). | ### Nested Shape: `Object.listViews[string]` diff --git a/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts b/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts index 2a18c6cc3ca..3b4880d8ed6 100644 --- a/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts +++ b/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts @@ -266,15 +266,13 @@ describe('[#8203] sys_activity.type — the writers emit declared values', () => }); /** - * The real default when a milestone omits `type`. Pinned because the spec's - * own field description says otherwise — `object.zod.ts` documents - * `activityMilestones[].type` as 'Activity type for the emitted row (default - * "completed")', while the code default is `activityTypeFor(action)` and a - * milestone can only fire on the UPDATE branch, making it `updated`. - * Filed separately; pinned here so the divergence is measured rather than - * argued from either side's prose. + * The real default when a milestone omits `type`: `activityTypeFor(action)`, + * and a milestone can only fire on the UPDATE branch, so the emitted row is + * `updated`. `object.zod.ts`'s `activityMilestones[].type` describe states + * this default (#20494); pinned here so the runtime behaviour stays + * measured, not just described. */ - it('a milestone without `type` emits `updated` — not the "completed" the spec text claims', async () => { + it('a milestone without `type` keeps the update row\'s kind, `updated`', async () => { const { engine, storeFor } = await boot(); await engine.insert('biz_ticket', { id: 't3', title: 'Three', stage: 'open' }); await moveStage(engine, 't3', 'plain'); diff --git a/packages/spec/src/data/object.zod.ts b/packages/spec/src/data/object.zod.ts index befde04ca8e..e5c742c1581 100644 --- a/packages/spec/src/data/object.zod.ts +++ b/packages/spec/src/data/object.zod.ts @@ -2120,7 +2120,7 @@ const ObjectSchemaBase = strictObject( field: z.string().describe('Field to watch (typically a status/stage select).').meta({ title: 'Field' }), value: z.string().describe('The value the field must transition INTO to fire the milestone.').meta({ title: 'Value' }), summary: z.string().describe('Activity summary template; {field} tokens interpolate the record value. e.g. "Deal won: {name}".').meta({ title: 'Summary' }), - type: z.string().optional().describe('Activity type for the emitted row (default "completed").').meta({ title: 'Type' }), + 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' }), })).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).'), // ADR-0020: record state machines are not a separate `stateMachines` map —