You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(rest): treatAsHistorical import option — skip the state machine for historical-data migration (#3479) (#3483)
Sibling of #3433 (seed exemption), one entry point over. #3165's `initialStates`
enforced the FSM entry point on every INSERT, so importing established historical
facts — already-`closed` tickets, `closed_won` deals, `completed` projects — was
rejected row-by-row with `invalid_initial_state`, blocking the core data-migration
path. Visible (per-row errors), unlike the silent seed case, but still a functional
block on a legitimate use.
- spec: `ExecutionContext.skipStateMachine` — general server-set flag (seedReplay's
sibling) skipping the `state_machine` rule for a write; `ImportRequestSchema.
treatAsHistorical` (default false) — the user-facing import option.
- objectql: engine skips the state machine for `seedReplay` OR `skipStateMachine`
(one `shouldSkipStateMachine` helper) — covers seed replay and historical import.
- rest: the import runner sets `skipStateMachine` on the write context iff the
request opts into `treatAsHistorical`; default off, so a normal import still walks
the FSM. Import undo also carries it now (restoring a prior snapshot re-writes an
earlier state that need not be a legal transition from the current one).
- platform-objects: `sys_import_job.treat_as_historical` audit column (additive).
Scope identical to the seed exemption: ONLY the `state_machine` rule is skipped;
field shape / format / cross_field / script all still run.
Regression tests (red-verified): engine.test — a `skipStateMachine` context bypasses
the FSM (sibling of the seedReplay case); import-runner-historical — the option maps
to `skipStateMachine` on the write context, gated (off by default), automation toggle
independent. The objectui import-wizard checkbox is a separate follow-up.
Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/protocol/objectql/state-machine.mdx
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -100,6 +100,7 @@ transitions: {
100
100
- The check is **lenient where it cannot reason**: if the prior state is not described by the table (e.g. legacy or externally-written data), it does not block.
101
101
- Only a rule with `severity: 'error'` (the default) blocks the write; `warning`/`info` are logged.
102
102
-**Seed writes are exempt** (#3433). Curated seed data — package bootstrap fixtures, marketplace templates, per-org replay, all loaded by `SeedLoaderService` — is a snapshot of established facts, not a record walking its lifecycle, so it bypasses the `state_machine` rule entirely: a seed may be born mid-lifecycle (a `completed` project, a `closed_won` opportunity) and neither `initialStates` (insert) nor `transitions` (update) is enforced. Every *other* validation still runs, so a seed must still satisfy field shape, `format`, `script`, and the rest. `os lint` warns when a seeded value is not a state the machine declares, so a typo is still caught before boot.
103
+
-**A "historical" data import is exempt too** (#3479). Migrating established facts — a batch of already-`closed` tickets, `closed_won` deals — is the same "snapshot, not a lifecycle event" situation. Set `treatAsHistorical: true` on the import request (default **off**) and the runner puts `skipStateMachine` on the write context, so `initialStates` doesn't reject those mid-lifecycle rows. A normal import leaves it off and still walks the FSM — the strict behavior is the default, so the exemption is always an explicit opt-in.
|**matchFields**|`string[]`| optional | Fields that identify an existing record (required for update/upsert) |
84
84
|**runAutomations**|`boolean`| ✅ | Fire triggers/hooks for each imported row (off by default for bulk) |
85
+
|**treatAsHistorical**|`boolean`| ✅ | Import as established historical facts: skip the state_machine rule so mid-lifecycle rows (e.g. already-closed tickets, closed_won deals) are not rejected by initialStates (#3479). Off by default so a normal import still walks the FSM. |
85
86
|**trimWhitespace**|`boolean`| ✅ | Trim leading/trailing whitespace from string cells |
|**matchFields**|`string[]`| optional | Fields that identify an existing record (required for update/upsert) |
372
373
|**runAutomations**|`boolean`| ✅ | Fire triggers/hooks for each imported row (off by default for bulk) |
374
+
|**treatAsHistorical**|`boolean`| ✅ | Import as established historical facts: skip the state_machine rule so mid-lifecycle rows (e.g. already-closed tickets, closed_won deals) are not rejected by initialStates (#3479). Off by default so a normal import still walks the FSM. |
373
375
|**trimWhitespace**|`boolean`| ✅ | Trim leading/trailing whitespace from string cells |
0 commit comments