@sanity/workflow-engine 0.27.0 → 0.29.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 +197 -0
- package/DATAMODEL.md +107 -0
- package/dist/_chunks-cjs/invariants.cjs +212 -442
- package/dist/_chunks-es/invariants.js +209 -405
- package/dist/define.cjs +1 -6
- package/dist/define.d.cts +232 -24
- package/dist/define.d.ts +232 -24
- package/dist/define.js +2 -7
- package/dist/index.cjs +1216 -510
- package/dist/index.d.cts +452 -34
- package/dist/index.d.ts +452 -34
- package/dist/index.js +1106 -415
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,202 @@
|
|
|
1
1
|
# @sanity/workflow-engine
|
|
2
2
|
|
|
3
|
+
## 0.29.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f44fc33: Workflow conditions can now read the caller's org-level User Attributes (Enterprise) through the advisory `$attributes` variable — for example `$attributes.department == "politics"` on a fireAction action filter, activity requirement, or editable predicate. Values match what lake role filters read via `user::attributes()`, keyed by attribute name with each active scalar or array value. Soft-gate paths (`evaluate`, fireAction filter re-check, editField) fetch the bag; ticks and drainers do not. Expected absences (401/402/403/404 — non-Enterprise, missing feature, forbidden) leave `$attributes` unbound so conditions that read it fail closed; unexpected fetch failures (5xx, network) throw on those soft-gate paths only and are not cached so the next call retries. An empty successful page binds `{}`. When the Management API envelope reports `hasMore: true`, the engine still binds the partial bag and warns (`nextCursor` makes follow-up pagination a small engine change). Engine checks remain advisory; the Content Lake is still the only enforcement point. `$attributes` is never persisted on `Actor` / history. The MCP authoring guide names the new variable alongside the other caller-bound vars. The test bench accepts an `attributes` bag (construction shortcuts and per-call overrides) the same way it accepts `grants`.
|
|
8
|
+
|
|
9
|
+
**No upgrade action required.** Existing definitions are unchanged. Authors who want attribute-gated actions opt in by writing `$attributes` in caller-bound condition sites; cascade gates continue to reject caller-bound vars at deploy.
|
|
10
|
+
|
|
11
|
+
**Docs impact:** Update the condition-variables reference (`docs/reference.md` § context-bound variables) and any guide that lists `$actor` / `$can` / `$assigned` so `$attributes` and the Enterprise / fail-closed semantics are named; note the spelling difference vs lake `user::attributes()`.
|
|
12
|
+
|
|
13
|
+
- 26f25e0: Definition authors and effect runtimes can now use `field.inc`, `field.dec`, and
|
|
14
|
+
`field.setIfMissing` operations. Arithmetic operations accept an optional
|
|
15
|
+
numeric value expression that defaults to `1`, validate number-field targets,
|
|
16
|
+
reject non-finite results, and participate in the same atomic engine commits as
|
|
17
|
+
existing field operations. A retry is deduplicated only when the caller supplies
|
|
18
|
+
an idempotency key; the arithmetic operation itself is not idempotent.
|
|
19
|
+
`field.setIfMissing` targets nullable fields; list fields are always arrays and
|
|
20
|
+
are rejected. When the target already has a value, `field.setIfMissing` does
|
|
21
|
+
nothing and writes no `opApplied` history row. Like Sanity Content Lake patches,
|
|
22
|
+
`field.inc` and `field.dec` require the target to already hold a number: a
|
|
23
|
+
declared number without an initial value is `null`, so arithmetic rejects without
|
|
24
|
+
committing. Initialize counters with `field.setIfMissing` set to `0` before
|
|
25
|
+
incrementing when the value may be absent.
|
|
26
|
+
|
|
27
|
+
Persisted-model version 6 adds the three operation discriminators. The change is
|
|
28
|
+
additive and the reader floor remains 4, so existing definitions and instances
|
|
29
|
+
require no migration. Before deploying a definition that uses these operations,
|
|
30
|
+
upgrade every Studio, CLI, MCP server, Function, and other runtime that can
|
|
31
|
+
process workflow data in the shared environment. The unchanged reader floor
|
|
32
|
+
does not fence out an older engine, which rejects or aborts when it encounters a
|
|
33
|
+
new operation rather than executing it with different semantics.
|
|
34
|
+
|
|
35
|
+
**Docs impact:** Update the Operations guide and operation reference with all
|
|
36
|
+
three forms, their defaults and validation, effect-completion availability, the
|
|
37
|
+
Sanity-compatible `field.setIfMissing(0)` then `field.inc` initialization
|
|
38
|
+
pattern, the warning that arithmetic never assumes a missing value is zero, and
|
|
39
|
+
the bounded-retry counting pattern that loops below a fixed total-attempt limit
|
|
40
|
+
and routes exhausted work to manual recovery. Note that no-op
|
|
41
|
+
`field.setIfMissing` attempts write no `opApplied` history row, and include the
|
|
42
|
+
readers-first rollout guidance for model 6 operations.
|
|
43
|
+
|
|
44
|
+
- 25bd1fb: Each card in the Studio plugin's Workflows tab now carries what the definition
|
|
45
|
+
is doing, not just its name: how many of its documents have work in flight, when
|
|
46
|
+
one of them last changed stage, how many hold a task past its due date, and one
|
|
47
|
+
alert glyph when something is wrong. The card reports live state only — what a
|
|
48
|
+
workflow runs on and what it says about itself change on a deploy, and that
|
|
49
|
+
workflow's own Definition page already names both. The
|
|
50
|
+
grid bands into "Assigned to you" and "Other workflows", so whether the reader
|
|
51
|
+
has work in a workflow is carried by where its card sits; rows keep the
|
|
52
|
+
catalog's alphabetical order inside each band. A card wears one alert, and it
|
|
53
|
+
means a run of that workflow cannot continue without help. The engine's
|
|
54
|
+
`blocked` readiness hold earns none, because it is an expected transient state
|
|
55
|
+
and flagging it would mark most cards most of the time; a rejected
|
|
56
|
+
document-type binding earns none either, because it belongs to the definition
|
|
57
|
+
rather than to any of its runs and is reported on that workflow's Definition
|
|
58
|
+
page.
|
|
59
|
+
|
|
60
|
+
The age on a card is a STAGE ARRIVAL, not a last-modified stamp. Every engine
|
|
61
|
+
commit moves an instance's change stamp — an assignment, a date, a drained
|
|
62
|
+
effect — and a workflow can absorb a great deal of that without advancing, so
|
|
63
|
+
the arrival is the event a catalog can read as progress. The consequence is
|
|
64
|
+
deliberate: a busy workflow held inside one stage reads as old, which is the
|
|
65
|
+
pathology worth seeing. Hovering that age names the move — which document went
|
|
66
|
+
to which stage, and the exact time — resolving the document's title through the
|
|
67
|
+
preview store only once the tooltip opens.
|
|
68
|
+
|
|
69
|
+
Overdue is the only signal drawn in a tone — caution, where the alert glyph
|
|
70
|
+
takes critical — and the only one that disappears when it reads zero: it reports
|
|
71
|
+
that something is wrong rather than how things stand, so a card with nothing
|
|
72
|
+
late spends no room saying so. A
|
|
73
|
+
document counts as overdue while an unsettled task on it is past its due date;
|
|
74
|
+
a finished task's date, however old, is not a debt. Overdue is measured on one
|
|
75
|
+
shared rule with the todo rows, so a card's count and a row's face can no
|
|
76
|
+
longer disagree about a single date.
|
|
77
|
+
|
|
78
|
+
The grid counts DOCUMENTS where it used to count instances, because its reader
|
|
79
|
+
is an editor and the document is what they open. Several instances on one
|
|
80
|
+
document count once. An instance whose chain references no document counts as
|
|
81
|
+
one unit of its own, so a workflow that runs on no document does not read as
|
|
82
|
+
idle. The workflow's own page still reports instances, in those words.
|
|
83
|
+
|
|
84
|
+
Hovering that count also says how many of those documents have yet to leave the
|
|
85
|
+
stage their workflow starts from — on a line of its own, so the two facts can be
|
|
86
|
+
scanned rather than read through, and folded into the first line when the answer
|
|
87
|
+
is all of them, where a second line would repeat one number. A document counts
|
|
88
|
+
there while any of its runs still waits at the start, judged against that run's
|
|
89
|
+
own pinned snapshot, so a run begun under an earlier version answers to where
|
|
90
|
+
that version started. The count stays in the hint rather than taking a slot on
|
|
91
|
+
the rule: it is a part of a number the card already shows, meaningless without
|
|
92
|
+
that total beside it, and no glyph in the set says "waiting at the start".
|
|
93
|
+
|
|
94
|
+
The page no longer opens on a project-wide total. The band headings count the
|
|
95
|
+
workflows, so a total above them stated the same figure twice, and a total of
|
|
96
|
+
documents invited a comparison against the cards that can never add up — one
|
|
97
|
+
document running two workflows is one document in a project total and one on
|
|
98
|
+
each of their cards.
|
|
99
|
+
|
|
100
|
+
The engine gains `documentStuckCause`, which classifies an instance as stuck
|
|
101
|
+
from its persisted document and pinned snapshot alone — no evaluation, no
|
|
102
|
+
reads, no actor. It answers for the effect- and activity-level causes
|
|
103
|
+
`diagnoseInstance` reports (`failed-effect`, `failed-activity`,
|
|
104
|
+
`hung-effect`) and returns `undefined` for the transition-level ones, which
|
|
105
|
+
need GROQ results. It is sound but incomplete by construction: a cause it
|
|
106
|
+
reports is real, and the absence of one means only that no cause was provable,
|
|
107
|
+
never that the instance is healthy. Surfaces flagging on it must not word an
|
|
108
|
+
unflagged state as an all-clear. `findActivityNode` and `findStageNode`, the
|
|
109
|
+
tolerant definition tree lookups it uses, are exported alongside it.
|
|
110
|
+
|
|
111
|
+
The tool's instance read is held per engine for the session and replayed on the
|
|
112
|
+
next mount, which changes what the Workflows tab paints while it loads. A first
|
|
113
|
+
visit now waits for that read before the definitions grid appears, where it used
|
|
114
|
+
to lay out cards from the catalog and rearrange them once the instances landed —
|
|
115
|
+
the grid's bands and counts come from the instances, so the cards had no stable
|
|
116
|
+
shape until both reads were in. The grid waits on the reader's identity for the
|
|
117
|
+
same reason: which band a card sits in depends on who is looking, and a
|
|
118
|
+
project-scoped account resolves through the project member directory, so banding
|
|
119
|
+
before that answer arrived would have moved every card once it did. An account
|
|
120
|
+
the directory cannot place, or a directory that fails to load, is an answer
|
|
121
|
+
rather than a wait — the grid paints unbanded and every card reports no assigned
|
|
122
|
+
work. A later visit inside the same session paints the
|
|
123
|
+
held read at once and refreshes behind it.
|
|
124
|
+
|
|
125
|
+
The form strip's assigned-task count tells those two apart as well, so an account
|
|
126
|
+
the member directory cannot place no longer leaves a spinner turning in that slot
|
|
127
|
+
for the rest of the session. It shows no count, which is what it already did for
|
|
128
|
+
an account with nothing assigned. The hold is not local to the grid: the
|
|
129
|
+
task rows under "All documents" and "For me", and the active-instance count on a
|
|
130
|
+
workflow's Definition page, read the same stream, so they too open on held
|
|
131
|
+
numbers before the fresh read lands. A failed read is never held, and a provider remount mints a new engine and
|
|
132
|
+
so starts cold.
|
|
133
|
+
|
|
134
|
+
**No upgrade action required.** The card additions are automatic, and
|
|
135
|
+
`documentStuckCause` is a new export that changes no existing verdict —
|
|
136
|
+
`diagnoseInstance` classifies exactly as before, now sharing one
|
|
137
|
+
implementation of the document-derivable causes.
|
|
138
|
+
|
|
139
|
+
**Docs impact:** Update the Workflows tool guide where it describes the
|
|
140
|
+
definitions grid, to cover the new card signals, the assigned-work ordering, the
|
|
141
|
+
document-versus-instance split between the grid and a workflow's own page, and
|
|
142
|
+
that the tab opens on a held read after the first visit.
|
|
143
|
+
`docs/reference.md` already carries `documentStuckCause` beside
|
|
144
|
+
`diagnoseInstance` with its bound stated; the hosted engine reference needs the
|
|
145
|
+
same entry, so integrators do not read a missing cause as a health check.
|
|
146
|
+
|
|
147
|
+
- 3fd04f1: **BREAKING:** `ActionSemantic` was a closed union of action decision values; it now also includes signal values and the open `custom.${string}` form. TypeScript consumers that exhaustively map `ActionSemantic` must handle signal and custom values or narrow to the decision subset before upgrading, otherwise their build will fail. Workflow authors can attach advisory signals and validated custom semantics to workflow roots, stages, activities, and actions, and evaluation results expose them at each matching level.
|
|
148
|
+
|
|
149
|
+
Existing stored definitions and runtime behavior remain compatible. The Studio plugin continues to render decision faces when decision values are composed with signal or custom semantics, while non-decision values receive the default action face.
|
|
150
|
+
|
|
151
|
+
**Docs impact:** Update the definition and evaluation references plus the concepts guide to document level-wide advisory semantics, the signal vocabulary, custom grammar, and action-only decision values.
|
|
152
|
+
|
|
153
|
+
## 0.28.0
|
|
154
|
+
|
|
155
|
+
### Minor Changes
|
|
156
|
+
|
|
157
|
+
- 5050b06: **BREAKING:** Authoring a deployment now types through `WorkflowDeploymentInput` / `WorkflowConfigInput`, which require `expectedMinReaderModel` as the current reader-floor literal. Passing a parsed `WorkflowDeployment` / `WorkflowConfig` (where the floor is optional/unverified) into `defineWorkflowConfig` or `defineWorkflows` is a type error — share the authored deployment object instead of `config.deployments[n]`. The same pin shows up on the blueprint resource: `EditorialWorkflowsResource.deployment` and `parseWorkflowDeployment`'s return type are now `AcknowledgedDeployment` (floor asserted to the current literal), so a hand-constructed provider resource must carry that literal too.
|
|
158
|
+
|
|
159
|
+
At runtime, config parse no longer rejects a stale or missing floor on an untargeted deployment. Only deployment-scoped commands assert, and only the selected deployment: definition deploy, `--check` / `--dry-run`, blueprint provision, `start`, `definition delete`, and `definition diff` (the last is a deployment-scoped read). Instance-id commands (`fire-action`, `abort`, `set-stage`, `reset-activity`) deliberately do not assert — they resolve by instance id, not by a declared deployment's acknowledgement — so an unacknowledged floor does not stop those instance commits.
|
|
160
|
+
|
|
161
|
+
Upgrade TypeScript configs and blueprint manifests that fed a parsed deployment into those helpers: keep a shared authored object (`satisfies WorkflowDeploymentInput`) for both `defineWorkflowConfig` and `defineWorkflows`, and give any hand-built `EditorialWorkflowsResource` the current floor literal on `deployment`. Existing configs that already acknowledge the current floor keep working at runtime; a stale selected deployment still fails with `ReaderModelAcknowledgementError` (headline + short steps; the CLI renders it through the clean styled-error path).
|
|
162
|
+
|
|
163
|
+
**Docs impact:** Update the CLI / blueprint authoring examples and the prerelease reader-floor guidance so they name `WorkflowConfigInput` / `WorkflowDeploymentInput` / `AcknowledgedDeployment`, forbid passing `config.deployments[n]` into `defineWorkflows`, state that instance-id writes are outside the acknowledgement gate, and stop implying whole-config parse-time rejection for commands that never select a deployment.
|
|
164
|
+
|
|
165
|
+
- a044ba5: Report a Studio start whose auto-advance lost a write race as the finished run
|
|
166
|
+
it is. Starting a workflow from the Studio while another runtime (a deployed
|
|
167
|
+
Function, a script) drives the same instance could hand the editor a warning
|
|
168
|
+
toast — "'…' started but didn't finish", with a raw `unexpected revision ID`
|
|
169
|
+
mutation error under it — for a run that had in fact completed: the Studio's own
|
|
170
|
+
auto-advance simply lost the write race to whoever committed the equivalent move
|
|
171
|
+
first. That toast was indistinguishable from a genuinely stuck workflow. The
|
|
172
|
+
Studio now confirms the completed start instead of warning about it.
|
|
173
|
+
|
|
174
|
+
Narrow by design: only a lost revision race whose run is already complete is
|
|
175
|
+
reclassified. A conflict that left the run in flight, and any other reason an
|
|
176
|
+
auto-advance failed, still surface as before.
|
|
177
|
+
|
|
178
|
+
`@sanity/workflow-engine` exports `isRevisionConflict`, the predicate that tells
|
|
179
|
+
a lost optimistic-locking race from a real error, so integrations can classify
|
|
180
|
+
one without matching on message text. It answers on the error alone, so narrow
|
|
181
|
+
to a single rev-guarded write before asking — a bare 409 also covers a create-id
|
|
182
|
+
collision.
|
|
183
|
+
|
|
184
|
+
**No upgrade action required.** Engine behaviour is unchanged and the new export
|
|
185
|
+
is additive.
|
|
186
|
+
|
|
187
|
+
**Docs impact:** `isRevisionConflict` is new public engine surface, added to the
|
|
188
|
+
errors section of `docs/reference.md` alongside the concurrency errors it
|
|
189
|
+
classifies; mirror that entry wherever published reference material lists the
|
|
190
|
+
engine's error helpers.
|
|
191
|
+
|
|
192
|
+
### Patch Changes
|
|
193
|
+
|
|
194
|
+
- 1e4a5da: When two runtimes both try to create the same stage guard after each observing it absent — for example a deployed Function reacting to instance commits while a script advances the same workflow — the losing create no longer aborts guard deploy. It falls through to the body-reconciling patch the existing-document path already uses, and writes its own resolution of the guard's conditions rather than assuming the two are identical. Concurrent deploys of an already-present guard were already a patch and are unchanged.
|
|
195
|
+
|
|
196
|
+
**No upgrade action required.**
|
|
197
|
+
|
|
198
|
+
**Docs impact: None** — `docs/reference.md` already documents `deployStageGuards` as an idempotent upsert.
|
|
199
|
+
|
|
3
200
|
## 0.27.0
|
|
4
201
|
|
|
5
202
|
### Minor Changes
|
package/DATAMODEL.md
CHANGED
|
@@ -702,6 +702,113 @@ its base `date` / `datetime`, so no older code path can write it while skipping
|
|
|
702
702
|
validation — there is no silent bypass to fence, and the kinds leave the floor
|
|
703
703
|
at 0.
|
|
704
704
|
|
|
705
|
+
### Model 6 — node semantics and field patch operations (reader floor: 4)
|
|
706
|
+
|
|
707
|
+
#### Semantics at every definition level
|
|
708
|
+
|
|
709
|
+
The optional non-empty `semantics[]` field now appears on the workflow root,
|
|
710
|
+
stages, and activities as well as actions. Its engine-owned vocabulary adds
|
|
711
|
+
`signal.positive`, `signal.caution`, and `signal.critical` at every level;
|
|
712
|
+
`custom.<meaning>` is an open, grammar-validated namespace at every level.
|
|
713
|
+
Actions retain their action-only `decision.accept` and `decision.decline`
|
|
714
|
+
values. The values remain advisory and change no execution, gating, transition,
|
|
715
|
+
status, history, or instance state. An absent field retains the generic behavior
|
|
716
|
+
of older definitions.
|
|
717
|
+
|
|
718
|
+
Manifest feature: `node-semantics` (definition, additive, detectable, floor 0).
|
|
719
|
+
|
|
720
|
+
Would an old reader misread (rule 5)? No. Definition content has no strict
|
|
721
|
+
read-side parse, and an older engine ignores the optional fields and new
|
|
722
|
+
advisory values. Definition documents are immutable after creation, so an older
|
|
723
|
+
writer never rewrites and drops them. The feature itself remains readable by
|
|
724
|
+
model-0 engines; new writers still stamp the unconditional writer minimum of 4.
|
|
725
|
+
This uses the existing `semantics` slot and extends that same word and shape to
|
|
726
|
+
the remaining definition nodes; a sibling property would duplicate the
|
|
727
|
+
advisory-meaning mechanism and violate the definition language's
|
|
728
|
+
self-similarity rules.
|
|
729
|
+
|
|
730
|
+
#### Field patch operations
|
|
731
|
+
|
|
732
|
+
The stored definition grammar additively admits `field.inc`, `field.dec`, and
|
|
733
|
+
`field.setIfMissing` action ops. Instances may embed the same definition tree
|
|
734
|
+
in `definitionSnapshot`, and effect-completion use records the new discriminator
|
|
735
|
+
in `history[]` rows' `opType`. Missing discriminators retain the historical op
|
|
736
|
+
vocabulary, so documents written before model 6 need no normalization. A
|
|
737
|
+
`field.setIfMissing` whose target already has a value writes no `opApplied` row:
|
|
738
|
+
persisting a targeted no-op would make older provenance and feed readers report
|
|
739
|
+
a field write that never happened.
|
|
740
|
+
|
|
741
|
+
Manifest feature: `field-patch-ops` (definition + instance,
|
|
742
|
+
additive, detectable, floor 0).
|
|
743
|
+
|
|
744
|
+
Would an old reader misread these ops (rule 5)? No. An older engine strictly
|
|
745
|
+
parsing or deploying a definition document rejects the unknown discriminator.
|
|
746
|
+
The instance runtime is different: `definitionSnapshot` is decoded without a
|
|
747
|
+
definition-schema parse, so an older engine can reach `applyOp`; its exhaustive
|
|
748
|
+
switch has no matching arm and the commit aborts when the missing summary is
|
|
749
|
+
used to build the audit row. That is a loud runtime failure rather than silent
|
|
750
|
+
execution with different semantics. Definition documents are create-only,
|
|
751
|
+
instance persists preserve the opaque `definitionSnapshot` string, and history
|
|
752
|
+
readers already treat `opType` as an opaque audit label, so the discriminator
|
|
753
|
+
survives an old writer's round trip. Rule 5 fences silent misinterpretation,
|
|
754
|
+
not loud refusal, therefore this detectable feature contributes floor 0 and the
|
|
755
|
+
writer maximum remains 4. The floor does not protect these definitions from
|
|
756
|
+
older runtimes: upgrade every runtime that can process shared workflow data
|
|
757
|
+
before adopting the new ops.
|
|
758
|
+
|
|
759
|
+
Why could the existing definition vocabulary not carry the behavior (definition
|
|
760
|
+
language rule 8)? Incrementing requires a read-modify-write primitive, while
|
|
761
|
+
`ValueExpr` deliberately describes context-free reads and has no arithmetic.
|
|
762
|
+
The existing `field.set` cannot atomically derive its value from the current
|
|
763
|
+
number. Conditional initialization also has no existing field-op equivalent:
|
|
764
|
+
an action-level `when` gates every op and effect in the action, while
|
|
765
|
+
`initialValue` runs only when an entry is first resolved and cannot initialize a
|
|
766
|
+
value cleared later by `field.unset`. These additions are new values in the
|
|
767
|
+
existing `ops[].type` slot and reuse the established field target and
|
|
768
|
+
value-expression shapes. Their abbreviated names deliberately mirror
|
|
769
|
+
`@sanity/client`'s patch vocabulary (`setIfMissing`, `inc`, `dec`) as one trio;
|
|
770
|
+
definition authors who know Sanity patches can predict the workflow spellings.
|
|
771
|
+
|
|
772
|
+
### Model 7 — the `$attributes` condition variable (reader floor: 4)
|
|
773
|
+
|
|
774
|
+
One additive growth of the condition dialect ships as model 7. It does not
|
|
775
|
+
raise the reader floor; the writer maximum stays 4.
|
|
776
|
+
|
|
777
|
+
A new caller-bound synthetic variable `$attributes` binds the acting token's
|
|
778
|
+
org-level User Attributes (Enterprise) as a flat key → active-value record —
|
|
779
|
+
the same values lake role filters read via `user::attributes()`, spelled for
|
|
780
|
+
the workflow condition dialect. Soft-gate paths resolve it when a
|
|
781
|
+
caller-bound projection runs (`evaluate` / fireAction filter re-check /
|
|
782
|
+
editField) — project → organization → global-host attributes page with
|
|
783
|
+
`limit=100`, no further pages; warn when the envelope reports `hasMore` —
|
|
784
|
+
parallel to grants on those paths, not on every `resolveAccess`. It is never
|
|
785
|
+
persisted on `Actor` / history, and left unbound (`undefined`) when the fetch
|
|
786
|
+
is an expected absence (401/402/403/404 — non-Enterprise org, missing feature,
|
|
787
|
+
forbidden) so conditions that read it fail closed — the same posture as `$can`
|
|
788
|
+
without grants. Unexpected fetch failures (5xx, network) throw and are not
|
|
789
|
+
cached, so the next soft-gate call retries; ticks and drainers never call the
|
|
790
|
+
endpoint. Legal sites match `$can` (fireAction action filters, activity
|
|
791
|
+
requirements, editable predicates, and those commit re-checks); cascade gates
|
|
792
|
+
and other caller-free sites deploy-reject it.
|
|
793
|
+
|
|
794
|
+
Manifest feature: `attributes-condition-var` (definition condition dialect,
|
|
795
|
+
additive, unconditional, floor 0).
|
|
796
|
+
|
|
797
|
+
Would an old reader misread the var (rule 5)? No — an older engine never binds
|
|
798
|
+
`$attributes`, so a GROQ read is null / fail-closed rather than a silent
|
|
799
|
+
misinterpretation of persisted trees. Definitions that mention `$attributes`
|
|
800
|
+
still deploy and evaluate on older engines; gates that depend on it simply
|
|
801
|
+
never pass until the fleet reads model 7. No reader-floor raise.
|
|
802
|
+
|
|
803
|
+
**Author-predicate shadowing (model ≤6 → 7):** `attributes` joins the reserved
|
|
804
|
+
condition-var names, so a **new** predicate named `attributes` is
|
|
805
|
+
deploy-rejected. A definition already stored under model ≤6 that declared
|
|
806
|
+
`predicates.attributes` is a different story: on a model-7 engine that binds
|
|
807
|
+
the caller bag, `$attributes` in that definition flips from “the author
|
|
808
|
+
predicate” to “the synthetic User Attributes bag” with no redeploy. Likely
|
|
809
|
+
zero real instances; no migration. Authors who hit it rename the predicate
|
|
810
|
+
and redeploy.
|
|
811
|
+
|
|
705
812
|
## Pending governed changes
|
|
706
813
|
|
|
707
814
|
- **`temp.system.guard` → `system.guard`** — the guard doc type's `temp.`
|