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
13 changes: 13 additions & 0 deletions .changeset/20494-milestone-type-default-describe.md
Original file line number Diff line number Diff line change
@@ -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.
2 changes: 1 addition & 1 deletion content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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]`

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/data/object.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 —
Expand Down
Loading