@sanity/workflow-engine 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +196 -0
- package/DATAMODEL.md +168 -0
- package/dist/_chunks-cjs/invariants.cjs +275 -94
- package/dist/_chunks-es/invariants.js +264 -89
- package/dist/define.d.cts +58 -40
- package/dist/define.d.ts +58 -40
- package/dist/index.cjs +1940 -901
- package/dist/index.d.cts +524 -236
- package/dist/index.d.ts +524 -236
- package/dist/index.js +1928 -911
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,201 @@
|
|
|
1
1
|
# @sanity/workflow-engine
|
|
2
2
|
|
|
3
|
+
## 0.21.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- d9394e5: **BREAKING:** Settle every child created by one spawn commit before the parent reacts to the cohort.
|
|
8
|
+
|
|
9
|
+
Parent transitions and triggered actions now observe the complete same-commit cohort instead of intermediate prefixes where later children are still unprimed. Spawn propagation stamps terminal child rows in one parent commit, cascade hops reuse an unchanged drain read, and successful aborts reuse their committed instance for immediate drain and ancestor propagation.
|
|
10
|
+
|
|
11
|
+
- f4bc057: **BREAKING:** `list_workflow_instances` now returns one page with `has_more` and an optional `next_cursor`; callers that need every match must continue with the cursor. The optional `limit` accepts 1–100 rows and defaults to 25.
|
|
12
|
+
|
|
13
|
+
Bound instance and definition list reads with lake-side filtering and consumer-specific projections.
|
|
14
|
+
|
|
15
|
+
Document filtering now excludes exited-stage references and includes live unresolved child-workflow references. Definition discovery selects and model-gates only the latest deployed version of each workflow name; historical versions remain stored and available to history-oriented APIs.
|
|
16
|
+
|
|
17
|
+
The MCP eval suite now verifies that agents follow list cursors to find a target beyond the first default page, including in persisted Braintrust runs.
|
|
18
|
+
|
|
19
|
+
MCP tool telemetry reports a `cursorUsed` boolean so continued list-page adoption is measurable without sending cursor values, arguments, or results.
|
|
20
|
+
|
|
21
|
+
- bcc30fe: **BREAKING:** One identity namespace inside the engine — every principal id in live state is the account-global user id (`sanityUserId`). The write boundary admits global-namespace ids only (`g…` humans, `p-…` robots verbatim; `e-…` third-party ids normalize to their embedded global form in the persisted output; plain project-scoped ids and unclassifiable forms are rejected with the remediation named). Actor resolution reads `/users/me` on both the workflow resource host and the global host: the global record is the canonical actor id, the resource host supplies roles, and the resource-local principal id rides the in-memory access context to bind `identity()` in guard previews and grant-filter evaluation — so advisory verdicts agree with the lake's own dialect, including per-resource identity for foreign-subject forecasts. Identity-kind guard `metadata` projections deploy in the target dataset's LOCAL spellings (translated through the project member list at deploy/refresh), so attribution predicates work without the author knowing two namespaces exist. Instances written before ids were namespace-classified resolve their live-state ids to global through the project-users directory at the read funnel (all-or-nothing per document, loud when unresolvable) and converge to global spellings on their next commit; `history[]` stays verbatim as audit record. Ships as data model 4 with reader floor 4: pre-classifier engines refuse new instances rather than mis-compare them, and deployment acknowledgements move to `expectedMinReaderModel: 4`. The prefix classifier is public API (`classifyPrincipalId`, `lakePrincipalId`, the `identity()` sentinels) so adapters can share the one namespace rule. **Existing instances written with project-scoped ids may no longer match user-identity queries and list filters until their next engine commit self-heals them; a prerelease deployment with no data worth keeping can reset with `sanity-workflows nuke` and start fresh.** There is no fallback to a project-scoped actor: when the workflow resource host reports a project-scoped principal, resolving the account-global record is required and its failure refuses the call. **Docs impact:** upgrade guidance is canonical in `docs/reader-model-rollout.md` § "Crossing to model 4" — any changelog-derived docs, upgrade notes, or assistant guidance should point there (readers-first sequencing, self-heal semantics, and the `sanity-workflows nuke` reset recommendation for prerelease data).
|
|
22
|
+
- e7392af: Add the `resetActivity` admin verb — reset a failed (or otherwise terminal) activity in an instance's current stage back to `active` (re-run) or `skipped` (bypass), then cascade so a `$allActivitiesDone`-gated exit transition can fire. `workflow.diagnose` now marks its `reset-activity` remediation `available`, and the CLI ships the real `reset-activity <instanceId> <activity> [--skip]` command in place of the stub. Emits the `Editorial Workflows Activity Reset` adoption event on every attempt (`changed: false` on a no-op), matching the `set-stage` and `abort` overrides.
|
|
23
|
+
|
|
24
|
+
Writes strictly within the existing model-3 grammar: it reuses the `activityStatusChanged` history variant and the existing activity statuses, so it adds no persisted shape — there is no data-model version bump and no reader-floor change.
|
|
25
|
+
|
|
26
|
+
- fa9c796: **BREAKING:** Replace the singular `start.allowed` and activity requirement record with ordered, named requirement arrays.
|
|
27
|
+
|
|
28
|
+
Start readiness now accepts polymorphic `groq` and `singleSubject` nodes:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Before
|
|
32
|
+
start: {allowed: '$fields.approved == true'}
|
|
33
|
+
|
|
34
|
+
// After
|
|
35
|
+
start: {
|
|
36
|
+
requirements: [
|
|
37
|
+
{type: 'groq', name: 'approved', title: 'Approval required', query: '$fields.approved == true'},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Use `singleSubject` instead of `$subjectHasInFlightInstance` to allow at most one in-flight run of the same definition for a subject. The requirement is definition-scoped and version-blind across deployments:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// Before
|
|
46
|
+
start: {allowed: '!$subjectHasInFlightInstance'}
|
|
47
|
+
|
|
48
|
+
// After
|
|
49
|
+
start: {
|
|
50
|
+
requirements: [
|
|
51
|
+
{type: 'singleSubject', name: 'single-subject', description: 'Finish the existing run first.'},
|
|
52
|
+
],
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Activity readiness uses the same ordered descriptor model with `groq` nodes:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// Before
|
|
60
|
+
requirements: {
|
|
61
|
+
approved: 'defined($fields.approval)'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// After
|
|
65
|
+
requirements: [{type: 'groq', name: 'approved', query: 'defined($fields.approval)'}]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Requirement names must be unique within their owning array. Evaluation preserves author order and reports every unmet requirement with its `name` and optional editor-facing `title` and `description`. Fresh standalone starts enforce the requirements after validating inputs. Resuming an unfinished start and parent-owned spawning continue to bypass start requirements.
|
|
69
|
+
|
|
70
|
+
`evaluateStart()` now returns ordered `requirements` entries containing each descriptor, outcome, and GROQ insight where applicable. Its singular top-level `insight` is removed. `StartNotAllowedError.insight` is replaced by `StartNotAllowedError.unmetRequirements`. Activity evaluation likewise reports unmet requirement descriptors instead of names alone.
|
|
71
|
+
|
|
72
|
+
Rename the public start-requirement analysis helpers and constants:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// Before
|
|
76
|
+
explainStartAllowed(args)
|
|
77
|
+
unboundAllowedReads(query, fields)
|
|
78
|
+
START_ALLOWED_VARS
|
|
79
|
+
|
|
80
|
+
// After
|
|
81
|
+
explainStartRequirement(args)
|
|
82
|
+
unboundRequirementReads(query, fields)
|
|
83
|
+
START_REQUIREMENT_VARS
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`definitionsForDocument({document, subject})` becomes `definitionsForDocument({document})`. Start applicability no longer needs a separately supplied subject. The engine-owned start dataset projection exposes only `definition`, `subject`, and `completedAt`; authors no longer query raw instance storage for deduplication.
|
|
87
|
+
|
|
88
|
+
Studio pre-flights both requirement kinds against the live document session, disables starts whose requirements are unmet, and renders the authored description or title with a humanized requirement-name fallback. Controls re-enable on the same mount when a blocking run completes.
|
|
89
|
+
|
|
90
|
+
Persisted data model 4 unconditionally raises `minReaderModel` to 4 for new definitions and instances because older readers would ignore readiness arrays and could commit invalid transitions. Upgrade every reader and Function before deploying model-4 writers. Legacy deployed definitions are not normalized; prerelease environments with incompatible definitions may use `sanity-workflows nuke` before redeploying.
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- 92e28bd: Fix reset telemetry test fixtures to use the current reader model.
|
|
95
|
+
|
|
96
|
+
## 0.20.0
|
|
97
|
+
|
|
98
|
+
### Minor Changes
|
|
99
|
+
|
|
100
|
+
- ab5e454: Export project-user response validation for host adapters that consume Sanity project-user APIs.
|
|
101
|
+
- e3122cc: New `instancesGuardQuery(instanceIds)` — the set-shaped guard filter (`sourceInstanceId in $instanceIds`, ordered by `_id`) reactive adapters subscribe as one shared live query per resource for every co-mounted session. `instanceGuardQuery(id)` delegates to it, so the per-instance and per-set reads share one filter definition; its compiled query string changes accordingly (same rows selected).
|
|
102
|
+
- 7c4dd86: Reduce guard lifecycle request volume and chunk orphan cleanup transactions.
|
|
103
|
+
|
|
104
|
+
### Patch Changes
|
|
105
|
+
|
|
106
|
+
- bce09fc: Restore automatic effect request tags while preserving concrete handler client APIs, backed by native request-prefix support in the test client.
|
|
107
|
+
- efb4cd9: Discriminator validation now reports an unknown field kind, field source, or op value expression at its exact `type` path instead of collapsing to "Invalid type: Expected Object but received Object". The schemas route on `type` rather than trying indistinguishable object arms in a plain union, so newer syntax used with an older engine identifies the unsupported value and the installed engine's valid options.
|
|
108
|
+
- 46b0285: **BREAKING:** Replace manually declared document applicability with automatic
|
|
109
|
+
discovery from deployed first-class subject fields.
|
|
110
|
+
|
|
111
|
+
Update Studio configuration as follows:
|
|
112
|
+
- `mappings` is optional. Omit it when every applicable definition declares a
|
|
113
|
+
first-class subject. A mapping may customize an automatically discovered
|
|
114
|
+
`(docType, definition)` binding or explicitly register a definition modeled
|
|
115
|
+
with a plain `doc.ref` instead of a first-class subject.
|
|
116
|
+
- Multiple workflows may target one document type. Use one mapping row for each
|
|
117
|
+
distinct `(docType, definition)` pair. An exact duplicate pair is a
|
|
118
|
+
configuration error rather than a last-row-wins override.
|
|
119
|
+
- Remove the top-level `autoStart` map or function. Put `autoStart: true` on
|
|
120
|
+
each mapping row that should start automatically. Configure workspace-specific
|
|
121
|
+
behavior in that workspace's mapping rows. This also works for explicitly
|
|
122
|
+
registered definitions using the `doc.ref` field named `subject` convention.
|
|
123
|
+
- Replace `workflowDefaultDocumentNode({mappings})` with
|
|
124
|
+
`workflowDefaultDocumentNode()`.
|
|
125
|
+
|
|
126
|
+
Before:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
structureTool({defaultDocumentNode: workflowDefaultDocumentNode({mappings})})
|
|
130
|
+
workflowStudioPlugin({
|
|
131
|
+
tag: 'production',
|
|
132
|
+
mappings,
|
|
133
|
+
autoStart: {article: ['article-review', 'legal-review']},
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
After:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
structureTool({defaultDocumentNode: workflowDefaultDocumentNode()})
|
|
141
|
+
workflowStudioPlugin({
|
|
142
|
+
tag: 'production',
|
|
143
|
+
mappings: [
|
|
144
|
+
{
|
|
145
|
+
docType: 'article',
|
|
146
|
+
definition: 'article-review',
|
|
147
|
+
label: 'Article review',
|
|
148
|
+
autoStart: true,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
docType: 'article',
|
|
152
|
+
definition: 'legal-review',
|
|
153
|
+
label: 'Legal review',
|
|
154
|
+
autoStart: true,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
})
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Also remove all per-schema Editorial Workflows preview wiring:
|
|
161
|
+
- Remove `components: {preview: WorkflowStagePreview}`.
|
|
162
|
+
- Remove `_id` added only for Editorial Workflows from `preview.select` and
|
|
163
|
+
stop passing it through `preview.prepare`.
|
|
164
|
+
- Remove imports of `WorkflowStagePreview`; that component is no longer a
|
|
165
|
+
public package export.
|
|
166
|
+
- Remove imports of `mappingForDocType` and `workflowDocTypes`; effective
|
|
167
|
+
mappings are resolved inside the plugin and those host-side helpers are no
|
|
168
|
+
longer exported.
|
|
169
|
+
- Keep each schema's native inferred or custom preview unchanged. The plugin
|
|
170
|
+
now installs preview middleware itself, consumes the document identity Studio
|
|
171
|
+
already supplies, and delegates title, subtitle, media, and custom preview
|
|
172
|
+
composition through `renderDefault`.
|
|
173
|
+
|
|
174
|
+
Workflow stage pills appear on Studio surfaces that invoke preview middleware,
|
|
175
|
+
including reference and array-item previews. Custom preview components that
|
|
176
|
+
replace Studio's layout must render the `status` prop they receive. Studio's
|
|
177
|
+
Structure document-list rows bypass both plugin and schema preview middleware,
|
|
178
|
+
so they do not show workflow stage pills. Workflow status remains available in
|
|
179
|
+
the document form, footer badge, Workflows view, and Workflows tool.
|
|
180
|
+
|
|
181
|
+
Document-reference GDRs for dataset content now reject stored draft IDs
|
|
182
|
+
(`drafts.<id>`) and Content Release version IDs
|
|
183
|
+
(`versions.<release>.<id>`). Use the stable document ID in the GDR and select
|
|
184
|
+
the draft or release through workflow perspective instead. The validation error
|
|
185
|
+
includes the corresponding stable ID so CLI and API callers can correct the
|
|
186
|
+
input before an unresolvable workflow instance is created.
|
|
187
|
+
|
|
188
|
+
Previously persisted draft/version GDRs remain invalid workflow identities.
|
|
189
|
+
Read-side Studio displays now tolerate them by showing the raw URI instead of
|
|
190
|
+
crashing, and query-sourced occurrences are discarded through the existing
|
|
191
|
+
fail-soft field-resolution path. Correct existing data by starting a new
|
|
192
|
+
instance with the stable document ID; perspective selects the desired draft or
|
|
193
|
+
release content.
|
|
194
|
+
|
|
195
|
+
- 8a76c67: Add incremental reactive-session document feeds, coalesce watched-document emissions, and reuse discovery derivations and Studio layout output. Deleted watched documents now leave the held snapshot instead of remaining as stale evaluation input. The component test runner also has aggregate-suite timeout headroom.
|
|
196
|
+
- 7e8f459: Batch snapshot hydration to reduce watched-document read latency.
|
|
197
|
+
- 98e488e: Stabilize Editorial Workflows discovery subscriptions and limit full reactive sessions to surfaces that need live evaluation.
|
|
198
|
+
|
|
3
199
|
## 0.19.0
|
|
4
200
|
|
|
5
201
|
### Minor Changes
|
package/DATAMODEL.md
CHANGED
|
@@ -152,6 +152,63 @@ never matches the new type).
|
|
|
152
152
|
direct reads at gate only (see "Consumer surfaces adopt the gate" above
|
|
153
153
|
for the split and its rationale).
|
|
154
154
|
|
|
155
|
+
## Definition-language philosophy
|
|
156
|
+
|
|
157
|
+
The authoring DSL is one language, not a bag of options. The principle of
|
|
158
|
+
least surprise applies to the WHOLE surface, and the test for any addition is
|
|
159
|
+
self-similarity: **someone who knows one part of the DSL should be able to
|
|
160
|
+
guess the rest.** Every change to the definition tree is designed against
|
|
161
|
+
these principles before the change-process rules below apply.
|
|
162
|
+
|
|
163
|
+
1. **Same concept, same word, same shape — at every level.** The gating
|
|
164
|
+
vocabulary is the canonical example: `filter` is **existence** (a
|
|
165
|
+
non-match might as well not exist — hidden, never merely disabled),
|
|
166
|
+
`requirements` is **readiness** (named readiness gates; an unmet entry
|
|
167
|
+
keeps the node visible but disabled, with the failing entries named),
|
|
168
|
+
`when` is **trigger**. A position that needs one of these concepts uses
|
|
169
|
+
the name and shape the concept has everywhere else; a concept that
|
|
170
|
+
changes name or shape between levels is a defect in the language, not a
|
|
171
|
+
local convenience.
|
|
172
|
+
2. **Surfaced things are nodes; mappings are internal.** Anything a person
|
|
173
|
+
will see — stages, activities, actions, transitions, fields, guards —
|
|
174
|
+
is an entry in a **polymorphic array of named objects**:
|
|
175
|
+
`{name, title?, description?, …}`, discriminated by `type` where
|
|
176
|
+
variants exist, so every surfaced entry can carry its own human copy.
|
|
177
|
+
Plain records (name → expression) are for internal mappings no surface
|
|
178
|
+
renders (predicates, effect bindings, role aliases). If an entry can be
|
|
179
|
+
shown to a person, it is an object in an array; a concept that outgrows
|
|
180
|
+
a record becomes an array of nodes, never a record of objects. Existence filters need none of this: a filtered-out entry
|
|
181
|
+
renders nothing, so there is nothing to title — bare condition strings
|
|
182
|
+
remain correct there.
|
|
183
|
+
3. **New capability is a new value in an existing slot, never a parallel
|
|
184
|
+
mechanism.** Extend the vocabulary of an existing slot — a new named
|
|
185
|
+
entry, a new declared value, a new synthetic variable — before adding a
|
|
186
|
+
sibling property. A proposal arriving with its own error type, its own
|
|
187
|
+
ordering rule against an existing gate, or its own verdict leg beside an
|
|
188
|
+
existing mechanism's is the smell that it duplicates that mechanism.
|
|
189
|
+
4. **Legibility comes from structure, not from bespoke props.** When a
|
|
190
|
+
surface needs to explain a refusal, the fix is named or structured slots
|
|
191
|
+
whose entries surfaces can address — never a one-off property added so
|
|
192
|
+
the engine can recognize a single policy. If the existing mechanism is
|
|
193
|
+
illegible or undiscoverable, restructure that mechanism. Authoring-time
|
|
194
|
+
discoverability can also live at the library layer — typed, documented
|
|
195
|
+
helpers that emit canonical expressions — before the model grows any
|
|
196
|
+
surface for it.
|
|
197
|
+
5. **If the gap is scoping, add a scope, not a mechanism.** A rule that
|
|
198
|
+
duplicates an existing behavior with different scoping (per-definition
|
|
199
|
+
vs cross-definition, per-subject vs per-run) is the existing mechanism
|
|
200
|
+
plus a scoping value or variable, not a new property.
|
|
201
|
+
6. **A persisted name is forever.** The shape ledger is append-only and
|
|
202
|
+
renames are forbidden (rule 3 below), so the name and shape of a new
|
|
203
|
+
definition-tree property are merge-blocking review concerns, not
|
|
204
|
+
bikeshedding. Names describe the check (`requirements`), never promise
|
|
205
|
+
an outcome the advisory engine cannot deliver (`unique`, `locked`).
|
|
206
|
+
Value vocabularies follow the established scheme — single lowercase
|
|
207
|
+
words, camelCase for multiword values (`todoList`, `fieldRead`), dotted
|
|
208
|
+
namespaces for families (`doc.ref`, `field.set`) — and nothing else: the
|
|
209
|
+
definition grammar has no kebab-case values, and no addition introduces
|
|
210
|
+
the first.
|
|
211
|
+
|
|
155
212
|
## Rules for changing the model
|
|
156
213
|
|
|
157
214
|
`DATA_MODEL_CHANGES` is the append-only, machine-readable counterpart of the
|
|
@@ -208,6 +265,11 @@ floor.
|
|
|
208
265
|
readers still work for those documents. Give the rollout order and scope:
|
|
209
266
|
readers-first before any writer can persist a floor-bearing feature, not a
|
|
210
267
|
misleading fleet-wide minimum when floor derivation is document-specific.
|
|
268
|
+
8. **A new definition-tree property justifies itself against the
|
|
269
|
+
definition-language philosophy above.** The model-log entry states why no
|
|
270
|
+
existing slot, value, or variable could carry the behavior. "The existing
|
|
271
|
+
mechanism is illegible or undiscoverable to surfaces" is an argument for
|
|
272
|
+
restructuring that mechanism, not for a sibling property beside it.
|
|
211
273
|
|
|
212
274
|
## Model log
|
|
213
275
|
|
|
@@ -498,6 +560,112 @@ writing, failing closed. A claim without a token (older writer's takeover)
|
|
|
498
560
|
simply cannot authorise mid-dispatch reports; dispatch and completion are
|
|
499
561
|
unaffected.
|
|
500
562
|
|
|
563
|
+
### Model 4 — classified principal ids (reader floor: 4)
|
|
564
|
+
|
|
565
|
+
One namespace change ships as model 4, and it is the first UNCONDITIONAL
|
|
566
|
+
reader-floor raise — model 2's floor-bearing features were detectable,
|
|
567
|
+
fencing only the documents that carried them; this one fences every
|
|
568
|
+
instance the engine writes: **every principal id the engine writes into
|
|
569
|
+
live state is the account-global user id** (`sanityUserId` — the `g…`-prefixed identity
|
|
570
|
+
that is stable across projects and org-level resources). Model 3 was
|
|
571
|
+
released before it landed, so it mints the next version.
|
|
572
|
+
|
|
573
|
+
The platform has two identity namespace families: project datasets speak
|
|
574
|
+
per-project principal ids (lake `identity()` returns the project user id — a
|
|
575
|
+
different value per project for the same person), while org-level resources
|
|
576
|
+
(Canvas, Media Library) and the global API host speak the account-global id.
|
|
577
|
+
Documents written before this model carry whichever namespace their engine's
|
|
578
|
+
client host happened to resolve — for dataset-anchored workflows, the
|
|
579
|
+
workflow resource project's local ids. The id string itself carries its
|
|
580
|
+
namespace (`g…` global, `p-…` robot, `e-<globalId>` third-party project form,
|
|
581
|
+
`p…` project-scoped, `<anonymous>`/`<system>` sentinels) — the discrimination
|
|
582
|
+
Sanity's own session middleware applies; `src/core/identity.ts` is the one
|
|
583
|
+
classifier.
|
|
584
|
+
|
|
585
|
+
**What changes.** No field is added, removed, or renamed — the stamped trees
|
|
586
|
+
are shape-identical to model 3. The declared change is the namespace
|
|
587
|
+
semantics of the principal-id VALUES in live state (`Assignee` user items,
|
|
588
|
+
`Actor` stamps in field values, `completedBy`), plus the read/write discipline
|
|
589
|
+
around them:
|
|
590
|
+
|
|
591
|
+
- **Write boundary** — identity kinds admit global-namespace ids only:
|
|
592
|
+
`g…` and `p-…` verbatim, `e-…` normalized to its embedded global id in the
|
|
593
|
+
persisted output; plain project ids and unclassifiable forms are rejected
|
|
594
|
+
with the remediation named. Every persisted id has passed classification —
|
|
595
|
+
the `e-…` rewrite is the only spelling change.
|
|
596
|
+
- **Read funnel (the versioned upgrade step)** — the instance point-read
|
|
597
|
+
resolves legacy project-scoped ids in live state to their global form
|
|
598
|
+
through the project-users directory (`/projects/<id>/users/<ids>`, the
|
|
599
|
+
sanctioned namespace bridge), ALL-OR-NOTHING per document: if any id
|
|
600
|
+
cannot be resolved the document is read as stored (comparisons against
|
|
601
|
+
those values fail closed, one loud warning). Because every commit persists
|
|
602
|
+
the in-memory instance wholesale, a resolved document's next commit
|
|
603
|
+
converges its stored spellings — normalize-on-rewrite IS this change's
|
|
604
|
+
migration mechanism; there is no separate tooling and no bulk migration.
|
|
605
|
+
- **The anchor-scope invariant** — a bare project-scoped id in stored live
|
|
606
|
+
state belongs to the workflow resource's own project (the document's home
|
|
607
|
+
resource). This held by construction for every pre-model-4 writer
|
|
608
|
+
(assignment surfaces loaded the anchor project's members; the actor
|
|
609
|
+
resolved against the anchor host), and the read funnel resolves under it.
|
|
610
|
+
- **`history[]` and `effectHistory[]` stay verbatim forever** — append-only
|
|
611
|
+
audit rows record who-acted-as-written; engine logic never compares
|
|
612
|
+
history actor identity for decisions (topology and provenance reads only),
|
|
613
|
+
and display resolves rows through the directory at presentation time.
|
|
614
|
+
|
|
615
|
+
**Why the floor rises (rule 5).** An older engine reading a model-4 instance
|
|
616
|
+
would not refuse it — it would MIS-COMPARE it: `$assigned` and the baked
|
|
617
|
+
claim gates are raw string equality, so a global-id assignee against an
|
|
618
|
+
older engine's project-scoped actor silently evaluates false (and the
|
|
619
|
+
mirror-image for legacy values under a new actor). Misinterpretation, not
|
|
620
|
+
parse failure — exactly the case the floor exists for. `minReaderModel: 4`
|
|
621
|
+
makes pre-classifier engines refuse the document loudly instead. The writer
|
|
622
|
+
maximum (`DATA_MODEL_MIN_READER`) rises to 4; deployment acknowledgements
|
|
623
|
+
follow the readers-first rollout (`docs/reader-model-rollout.md`).
|
|
624
|
+
|
|
625
|
+
Manifest feature: `classified-principal-ids` (instance, reader-floor,
|
|
626
|
+
**unconditional** — every instance this engine writes stamps actor identity
|
|
627
|
+
in the global namespace, so no instance persist can leave the floor below 4;
|
|
628
|
+
definitions carry no principal ids and are unaffected).
|
|
629
|
+
|
|
630
|
+
Round-trip survival: not applicable in the old-writer direction — the floor
|
|
631
|
+
raise means a pre-model-4 writer refuses the document before any write; the
|
|
632
|
+
one legal mixed state is old documents under new readers, which the read
|
|
633
|
+
funnel owns.
|
|
634
|
+
|
|
635
|
+
#### Model 4 also adds named polymorphic readiness requirements
|
|
636
|
+
|
|
637
|
+
Readiness becomes an ordered array of named nodes. `start.requirements`
|
|
638
|
+
accepts authored GROQ nodes and the declared `singleSubject` node;
|
|
639
|
+
`activity.requirements` becomes an array of named GROQ nodes. Each node may
|
|
640
|
+
carry editor-facing `title` and `description` metadata. The singular
|
|
641
|
+
`start.allowed` and the activity requirement record are removed.
|
|
642
|
+
|
|
643
|
+
- **Definition tree** — start and activity readiness use requirement arrays
|
|
644
|
+
discriminated by `type`; names are unique within their owning array.
|
|
645
|
+
- **Instance definition snapshot** — the same definition tree is pinned into
|
|
646
|
+
every new instance. Readers normalize legacy activity requirement maps in
|
|
647
|
+
existing snapshots into named GROQ nodes before evaluation; newly persisted
|
|
648
|
+
snapshots use only the array form.
|
|
649
|
+
|
|
650
|
+
Manifest feature: `readiness-requirements` (definition, reader-floor,
|
|
651
|
+
detectable, floor 4).
|
|
652
|
+
|
|
653
|
+
Model 4 is the default floor after this change, not a compatibility mode for
|
|
654
|
+
legacy deployed definitions. Deploy the model-4 definition set before running
|
|
655
|
+
the new reader; environments that retain incompatible earlier definitions
|
|
656
|
+
should follow the documented destructive reset (`nuke`) rollout path instead
|
|
657
|
+
of expecting those definitions to be normalized on read.
|
|
658
|
+
|
|
659
|
+
Would an old reader misread the field (rule 5)? Yes. An older engine ignores
|
|
660
|
+
the new arrays and therefore treats gated starts and activities as ready,
|
|
661
|
+
which changes the state-machine decisions it may commit. Missing arrays keep
|
|
662
|
+
their historical meaning (no requirements), but a present array requires a
|
|
663
|
+
model-4 reader. The writer floor is raised unconditionally to 4 because every
|
|
664
|
+
new instance embeds a model-4 definition snapshot and can later be evaluated
|
|
665
|
+
from that snapshot. Definitions are create-only and instance persists retain
|
|
666
|
+
unknown fields, so model-4 data survives round trips; the floor prevents an
|
|
667
|
+
older engine from interpreting it in the first place.
|
|
668
|
+
|
|
501
669
|
## Pending governed changes
|
|
502
670
|
|
|
503
671
|
- **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`
|