@supernovae-st/nika 0.118.7 → 0.120.2
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/README.md +649 -162
- package/dist/index.cjs +809 -144
- package/dist/index.d.cts +315 -19
- package/dist/index.d.ts +315 -19
- package/dist/index.js +808 -144
- package/docs/architecture.md +75 -12
- package/docs/http-api.md +81 -8
- package/docs/migrating-to-0.116.md +21 -6
- package/docs/testing.md +27 -2
- package/openapi.json +1797 -1
- package/package.json +9 -8
package/docs/architecture.md
CHANGED
|
@@ -26,14 +26,28 @@ local nika process authenticated nika serve
|
|
|
26
26
|
Adapters must implement and makes unsupported authority explicit.
|
|
27
27
|
- `src/lib/native-process-transport.ts` is the local Adapter. It spawns the
|
|
28
28
|
selected engine without a shell and consumes newline-delimited machine
|
|
29
|
-
events.
|
|
29
|
+
events. It reads the first machine frame before it returns a run, so
|
|
30
|
+
`run()` resolves on the engine's admission and rejects on its refusal.
|
|
31
|
+
- `src/lib/run-refusal.ts` reads the engine's one pre-run refusal object (its
|
|
32
|
+
check report carrying `clean: false`, or its `{ error }` envelope) and turns
|
|
33
|
+
it into the typed `NikaOperationError`. It judges nothing: a shape the
|
|
34
|
+
engine was not measured to write is a protocol fault, never a refusal.
|
|
35
|
+
- `src/lib/legacy-run-refusals.ts` is temporary. It recovers the three refusal
|
|
36
|
+
dialects of engines up to 0.119.0, which predate the one `run --json`
|
|
37
|
+
grammar, and is deleted with its call sites once no supported engine writes
|
|
38
|
+
them.
|
|
30
39
|
- `src/lib/http-transport.ts` is the remote Adapter. It verifies the remote
|
|
31
40
|
server identity once per client, resolves and verifies a local engine only
|
|
32
41
|
for caller-owned snapshot capture, then uses the authenticated HTTP
|
|
33
42
|
contract.
|
|
34
43
|
- `src/lib/run-session.ts` is the lifecycle Seam. It owns the eager event
|
|
35
44
|
pump, bounded independent observers, cancellation memoization, and the sole
|
|
36
|
-
terminal
|
|
45
|
+
terminal settlement. It builds the `NikaRun` handle as closures over itself,
|
|
46
|
+
so the Run owns `events()`, `result()`, `status()` and `cancel()`.
|
|
47
|
+
- `src/lib/run-events.ts` is the semantic Adapter: a pure projection of one
|
|
48
|
+
protocol frame onto the SDK's lifecycle vocabulary, with the frame kept by
|
|
49
|
+
identity on `raw`. It is applied at the edge of an observer view. There is
|
|
50
|
+
one history and one bounded queue per observer, and no second event bus.
|
|
37
51
|
- `openapi.json` and `src/generated/openapi.d.ts` pin the HTTP contract judged
|
|
38
52
|
by CI. `scripts/check-sdk-coverage.js` fails if a live runtime path is
|
|
39
53
|
missing or if the SDK names a path outside that contract.
|
|
@@ -62,19 +76,68 @@ Some operations deliberately have one authority:
|
|
|
62
76
|
## Lifecycle invariants
|
|
63
77
|
|
|
64
78
|
1. `run()` resolves only after stable admission and returns an immutable
|
|
65
|
-
`
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
`NikaRun` handle: `id`, `events()`, `result()`, `status()`, `cancel()`, and
|
|
80
|
+
`done`, the compatibility alias of `result()`. Admission is not execution;
|
|
81
|
+
a succeeded `result()` is not a sealed receipt (`traceVerify` can return
|
|
82
|
+
`receipt_mismatch` on an UNSEALED journal). Its members are closures
|
|
83
|
+
over the run's one session, so an extracted method still works. The handle
|
|
84
|
+
owns the lifecycle and nothing else: checking, proof, catalogs and
|
|
85
|
+
authoring stay on the facade. A run handle never means "maybe a run": a refusal
|
|
86
|
+
before admission rejects `run()` with `NikaOperationError` and no handle
|
|
87
|
+
exists. Over HTTP a red local snapshot refuses before any request is sent;
|
|
88
|
+
on the native transport the first machine frame decides. A run event is the
|
|
89
|
+
engine's admission evidence and is replayed as the first event; a refusal
|
|
90
|
+
object is the whole stream. The one spawn that executes the workflow is the
|
|
91
|
+
one that judges it: the SDK adds no preflight check and never spawns twice.
|
|
92
|
+
2. `run.result()` is the only terminal promise (`run.done` is the same one).
|
|
93
|
+
Workflow failure is result data; configuration, transport, protocol, and
|
|
94
|
+
compatibility failures throw. A `paused` result is a human gate: neither a
|
|
95
|
+
failure nor a completed execution.
|
|
96
|
+
3. A session's history retains at most `eventBufferSize` frames (default 4096,
|
|
97
|
+
never unbounded; each frame is bounded by `machineBufferBytes`). That
|
|
98
|
+
product bounds the retained history only, not the session or the process:
|
|
99
|
+
frames already handed to a consumer, other views and other runs are not
|
|
100
|
+
counted in it. Two different
|
|
101
|
+
bounds can be exceeded and they are never confused: a live view that falls
|
|
102
|
+
behind fails with `reason: 'live_backpressure'`, and a view opened after
|
|
103
|
+
more frames than it can be given is refused with
|
|
104
|
+
`reason: 'replay_truncated'` plus what was `observed` and `retained`.
|
|
105
|
+
Neither skips a frame, neither touches another view, and neither touches
|
|
106
|
+
the result: a run longer than the bound still settles normally. The
|
|
107
|
+
session is memory over what this process saw. It is not a control plane,
|
|
108
|
+
keeps nothing on disk, and reads no engine journal to extend a replay.
|
|
109
|
+
`run.events()` creates an independent bounded observer in the lifecycle
|
|
110
|
+
vocabulary. Aborting an observer never cancels the run. The vocabulary is a
|
|
111
|
+
stateless per-frame projection: it names a fact only for a (kind, status)
|
|
112
|
+
pair a producer defines, never defaults a state word, never deduplicates,
|
|
113
|
+
and never synthesizes a frame a transport did not emit. Every other frame
|
|
114
|
+
keeps flowing as `engine.event`, so transports differ in cardinality but
|
|
115
|
+
never in names, and `event.raw` is always the untouched protocol frame.
|
|
116
|
+
4. `run.cancel()` is idempotent per run: every call returns the one request.
|
|
117
|
+
5. `run.status()` reads the durable HTTP projection and refuses a native-only
|
|
72
118
|
run instead of guessing from local process state.
|
|
73
119
|
6. Schedule creation uses `If-None-Match: *`; updates require the exact opaque
|
|
74
120
|
revision in `If-Match`.
|
|
75
|
-
7. `attachRun()`
|
|
76
|
-
initial sequence is caller-owned durable
|
|
77
|
-
from in-memory SDK history.
|
|
121
|
+
7. `attachRun()` is the one recovery door. It creates a fresh owned session
|
|
122
|
+
for an existing HTTP job; its initial sequence is caller-owned durable
|
|
123
|
+
checkpoint state, never inferred from in-memory SDK history. A native run
|
|
124
|
+
is process-bound: its `run.id` is an ephemeral correlation id, and the SDK
|
|
125
|
+
keeps no local job store, starts no hidden `nika serve`, and reads no
|
|
126
|
+
journal to fake durability.
|
|
127
|
+
8. Ownership has no registry. The facade maps each handle it created to its
|
|
128
|
+
session in a `WeakMap`. The deprecated `nika.events(run)`,
|
|
129
|
+
`nika.cancel(run)` and `nika.status(run)` wrappers resolve through it, so a
|
|
130
|
+
foreign, reconstructed, serialized, or other-client handle still throws
|
|
131
|
+
`NikaRunOwnershipError`. `nika.events(run)` keeps yielding the protocol
|
|
132
|
+
vocabulary unchanged. The wrappers stay for one release train counted from
|
|
133
|
+
publication: they ship unchanged in the first published train that carries
|
|
134
|
+
the Run-owned lifecycle, the earliest train that may remove them is the one
|
|
135
|
+
after it, and the removal is the One SDK baseline owner's decision (#114),
|
|
136
|
+
announced in that train's release notes. No version or date is fixed.
|
|
137
|
+
9. An interrupted observation is a typed transport error carrying its cursor
|
|
138
|
+
(`NikaObservationInterrupted`), never an engine failure: the run may still
|
|
139
|
+
be running. The engine's own `interrupted` evidence state is data instead:
|
|
140
|
+
a `run.interrupted` event and `result.status === 'interrupted'`.
|
|
78
141
|
|
|
79
142
|
## Deletion test
|
|
80
143
|
|
package/docs/http-api.md
CHANGED
|
@@ -18,9 +18,9 @@ Any other non-2xx body is discarded and reported as a redacted
|
|
|
18
18
|
| `POST /v1/check` | `check()` | validates a served name or immutable snapshot bytes without a job |
|
|
19
19
|
| `POST /v1/jobs` | `run()` | admits a served name or exact snapshot bytes with an idempotency key |
|
|
20
20
|
| `GET /v1/jobs/{id}` | internal settlement | durable job identity, outputs, receipt, settlement, or redacted error |
|
|
21
|
-
| `GET /v1/jobs/{id}/status` | `status(
|
|
22
|
-
| `GET /v1/jobs/{id}/events` | `events(
|
|
23
|
-
| `POST /v1/jobs/{id}/cancel` | `cancel(
|
|
21
|
+
| `GET /v1/jobs/{id}/status` | `run.status()` | current status only |
|
|
22
|
+
| `GET /v1/jobs/{id}/events` | `run.events()` / `attachRun()` | bounded, sequenced SSE with replay |
|
|
23
|
+
| `POST /v1/jobs/{id}/cancel` | `run.cancel()` | 200 a settled job or its terminal replay; 202 the request accepted on a running job, settled later by observation |
|
|
24
24
|
| `GET /v1/jobs/{id}/trace/verify` | `traceVerify(receipt)` | engine-owned typed trace verdict; `reason` only on a verdict that does not hold |
|
|
25
25
|
| `GET/PUT /v1/schedules/{id}` | `scheduleStatus()` / `schedule()` | resident schedule projection and CAS mutation |
|
|
26
26
|
|
|
@@ -35,12 +35,25 @@ Any other non-2xx body is discarded and reported as a redacted
|
|
|
35
35
|
- Each request has a bounded timeout and each JSON/SSE machine frame has a
|
|
36
36
|
byte ceiling.
|
|
37
37
|
- Remote `check()` refuses `model` and `nativeStrict`; remote `run()` refuses
|
|
38
|
-
`
|
|
38
|
+
`model`, `maxCostUsd` and the deprecated `vars` until a request envelope
|
|
39
|
+
owns them.
|
|
40
|
+
- Remote `run()` sends `inputs` as `JobByName.inputs` only for a served name
|
|
41
|
+
and only when `GET /health` advertises `jobInputs`. That capability, not a
|
|
42
|
+
202, is the negotiation: a resident from before the envelope accepts the
|
|
43
|
+
extra field and ignores its values, so the SDK refuses it after `/health`
|
|
44
|
+
alone with `NikaCompatibilityError` (`capability: 'jobInputs'`). The
|
|
45
|
+
`inputs` envelope is engine-owned (nika#1642) and lands in the pinned
|
|
46
|
+
`openapi.json` when the engine pin reaches a release that serves it.
|
|
47
|
+
- A snapshot body takes no `inputs` overlay, an empty map included: `run()` of
|
|
48
|
+
a local path with `inputs` is refused before any capture or request.
|
|
49
|
+
- The serialized `inputs` map is bounded at 1 MiB by the SDK, the bound the
|
|
50
|
+
native channel reads. The resident's whole-request ceiling is its own and may
|
|
51
|
+
be lower.
|
|
39
52
|
- Caller-provided workflow catalog names must be contained slash-separated
|
|
40
53
|
paths. Absolute paths, backslashes, empty segments, `.` and `..` are
|
|
41
54
|
rejected before network I/O.
|
|
42
55
|
|
|
43
|
-
A contained `.nika
|
|
56
|
+
A contained `.nika` name uses the resident registry without a local
|
|
44
57
|
engine. Prefix a local file with `./` to capture and submit its snapshot.
|
|
45
58
|
A successful by-name check returns `clean: true` and the compact resident
|
|
46
59
|
acknowledgement; no local check report or exit code is fabricated.
|
|
@@ -55,6 +68,62 @@ whose `status` contradicts the record carrying it, keeps fields it does not
|
|
|
55
68
|
know, and never derives a settlement from an exit code; a job the resident
|
|
56
69
|
lost (`interrupted`) carries none.
|
|
57
70
|
|
|
71
|
+
## Frame time and journal evidence (engine main)
|
|
72
|
+
|
|
73
|
+
Both resident projections are closed, and the SDK refuses any field it does
|
|
74
|
+
not know. Engine main adds two optional fields that are
|
|
75
|
+
ahead of the pinned `openapi.json`: no released engine writes them yet, and a
|
|
76
|
+
resident that predates them never sends them, so nothing changes against a
|
|
77
|
+
released resident. They are read so that a resident built from engine main can
|
|
78
|
+
be observed at all; the pin itself moves only with a release.
|
|
79
|
+
|
|
80
|
+
- `JobEvent.at` is when the resident admitted the event: an RFC 3339 timestamp
|
|
81
|
+
in UTC, outside the event's hash chain. It rides `event.raw.at` untouched.
|
|
82
|
+
Anything that is not such a timestamp is a `NikaProtocolError`. The durable
|
|
83
|
+
`Job` declares no `at`, so one there is still an unknown field.
|
|
84
|
+
- `evidence`, on the terminal frame and on the durable `Job`, reports that the
|
|
85
|
+
run's journal mirror stopped recording. It is exactly a `status` and a
|
|
86
|
+
`reason`. The one status is `mirror_lost`. The reason is `write_failed`
|
|
87
|
+
(opening, writing or syncing the journal failed) or `record_refused` (a
|
|
88
|
+
record could not be admitted within the writer's bounds): a coarse class,
|
|
89
|
+
never OS text and never a path. Any other shape or word is a
|
|
90
|
+
`NikaProtocolError`, as the engine itself refuses one, and its value is
|
|
91
|
+
never quoted in the error.
|
|
92
|
+
|
|
93
|
+
`evidence` is independent of the execution and is never a verdict: a run can
|
|
94
|
+
settle `succeeded` while its mirror is lost. It never changes `result.status`,
|
|
95
|
+
the settlement, or the receipt's identity checks. `run.result()` copies it to
|
|
96
|
+
`result.evidence` from the frame or record that settled the run, so a caller
|
|
97
|
+
who never iterates events still learns the trace may be incomplete before
|
|
98
|
+
trusting `traceVerify`. Its absence claims nothing: not that a journal exists,
|
|
99
|
+
only that no loss was reported. A native run never carries it.
|
|
100
|
+
|
|
101
|
+
## Lifecycle vocabulary over the resident's frames
|
|
102
|
+
|
|
103
|
+
`run.events()` names the resident's closed `JobEvent` frames in the SDK's
|
|
104
|
+
lifecycle vocabulary and keeps each frame on `event.raw`:
|
|
105
|
+
|
|
106
|
+
| `JobEvent.kind` | `JobEvent.status` | `event.kind` |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `execution.started` | any | `run.started` |
|
|
109
|
+
| `execution.settled` | `paused` | `run.waiting` |
|
|
110
|
+
| `execution.settled` | `succeeded` · `failed` · `cancelled` | `run.settled` |
|
|
111
|
+
| `execution.cancelled` | `cancelled` only | `run.settled` |
|
|
112
|
+
| `execution.refused` | `failed` only | `run.settled` |
|
|
113
|
+
| `execution.interrupted` · `interrupted` | `interrupted` | `run.interrupted` |
|
|
114
|
+
| anything else: a `null` or future kind; an end kind whose status is absent, `null`, future, `queued` or `running`; or a pair that contradicts itself (`execution.refused` carrying `succeeded` or `cancelled`, `execution.cancelled` carrying `succeeded` or `failed`, an end kind carrying `interrupted`) | | `engine.event` |
|
|
115
|
+
|
|
116
|
+
The pairs above are exhaustive and listed, never computed as a product of
|
|
117
|
+
kinds and words. An unnamed frame is still delivered with `event.raw` intact,
|
|
118
|
+
and `run.result()` still reads the state word the engine wrote on it.
|
|
119
|
+
|
|
120
|
+
The resident streams no per-task frame, so an HTTP run yields no `task.*`
|
|
121
|
+
event: the SDK never synthesizes one. `event.sequence` is the validated SSE
|
|
122
|
+
id, the cursor to persist. The engine's `interrupted` (execution ownership
|
|
123
|
+
was lost, settlement unknown) is data: a `run.interrupted` event and
|
|
124
|
+
`result.status`. It is unrelated to `NikaObservationInterrupted` below, which
|
|
125
|
+
is this client losing its view of a run that may still be running.
|
|
126
|
+
|
|
58
127
|
## SSE recovery
|
|
59
128
|
|
|
60
129
|
The client checks that SSE ids are canonical positive integers and equal
|
|
@@ -71,9 +140,13 @@ A cursor means “fully processed”, not merely “received”.
|
|
|
71
140
|
|
|
72
141
|
## Idempotency and schedules
|
|
73
142
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
143
|
+
HTTP `run()` requires a caller-owned `idempotencyKey` of 1–255 bytes. Omitting
|
|
144
|
+
it throws `NikaConfigurationError` before network I/O or local snapshot capture.
|
|
145
|
+
Persist the key before admission and retry the exact request with the same key
|
|
146
|
+
if the response is lost or times out. The SDK never generates a hidden key or
|
|
147
|
+
retries admission automatically. Reusing a key with different request bytes
|
|
148
|
+
is an engine conflict, not a retry success. Direct native runs still omit the
|
|
149
|
+
key and reject it if supplied.
|
|
77
150
|
|
|
78
151
|
The namespace is the whole durable job store under the server's configured
|
|
79
152
|
`state-root`, across workflows, clients, schedules, and server restarts. The
|
|
@@ -64,9 +64,20 @@ invariants; observe or cancel a returned run through its owned lifecycle.
|
|
|
64
64
|
| `nika.workflows.list()` | `nika.listWorkflows()` |
|
|
65
65
|
| `nika.workflows.metadata(name)` | `nika.workflow(name)` |
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
In 0.116 the run handle was only `{ id, done }`. The Run now owns its
|
|
68
|
+
lifecycle: `run.events()`, `run.result()`, `run.status()` and `run.cancel()`,
|
|
69
|
+
with `run.done` kept as the alias of `run.result()`. The `nika.events(run)`,
|
|
70
|
+
`nika.cancel(run)` and `nika.status(run)` forms this guide shows still work
|
|
71
|
+
unchanged as deprecated wrappers, and `nika.events(run)` still yields the
|
|
72
|
+
protocol vocabulary; `run.events()` yields one lifecycle vocabulary on both
|
|
73
|
+
transports with that frame on `event.raw`. The wrappers stay for one release
|
|
74
|
+
train counted from the first published train that carries the new API, with
|
|
75
|
+
no version or date fixed; "Migrating to the Run-owned lifecycle" in the
|
|
76
|
+
README defines that window.
|
|
77
|
+
|
|
78
|
+
The client-level methods reject a look-alike object and a run from another
|
|
79
|
+
client, so persist the job id and reattach after a process restart instead of
|
|
80
|
+
rebuilding a handle by hand.
|
|
70
81
|
|
|
71
82
|
`LocalNika.version()`, `dryRunPlan()`, and `test()` have no One SDK method in
|
|
72
83
|
0.116. Keep those CLI-facing probes in deployment/CI (`nika --version`,
|
|
@@ -92,8 +103,12 @@ both versions.
|
|
|
92
103
|
|
|
93
104
|
`runToEnd()` returned `{ ok, exitCode, events[] }` with every event buffered.
|
|
94
105
|
`run.done` returns the terminal result only; the events ride `events(run)`
|
|
95
|
-
as a bounded
|
|
96
|
-
|
|
106
|
+
as a bounded iterator. In 0.116 a session retained 256 frames, below the 273 a
|
|
107
|
+
measured clean native run of 90 `mock/echo` tasks writes, so such a run had to
|
|
108
|
+
be observed concurrently or its prefix was gone. The default is now 4096 and a view opened
|
|
109
|
+
after the result replays every retained frame; past the bound it is refused
|
|
110
|
+
with `reason: 'replay_truncated'`, never shortened, and the result is
|
|
111
|
+
unaffected. An explicit `eventBufferSize` keeps its cap. The removed methods (`version()`, `dryRunPlan()`, path-based
|
|
97
112
|
`traceVerify()`) are absent, not stubbed: calling them throws a plain
|
|
98
113
|
`TypeError: … is not a function`, and a trace that only exists as a file
|
|
99
114
|
path in a later process has no SDK verification door in 0.116.
|
|
@@ -111,7 +126,7 @@ These methods require an HTTP client. A native-process client returns a typed
|
|
|
111
126
|
## Durable status
|
|
112
127
|
|
|
113
128
|
```ts
|
|
114
|
-
const run = await nika.run('flow.nika
|
|
129
|
+
const run = await nika.run('flow.nika');
|
|
115
130
|
console.log(await nika.status(run));
|
|
116
131
|
console.log(await run.done);
|
|
117
132
|
```
|
package/docs/testing.md
CHANGED
|
@@ -52,7 +52,15 @@ starts is a 200 `cancelled` whose terminal is one of those two writer kinds,
|
|
|
52
52
|
only with `status=cancelled`. The verifiers bind each cancel reply to the
|
|
53
53
|
terminals it may lead to, demand the run status of that terminal, and refuse any
|
|
54
54
|
other pairing. The parsed deterministic and packed-project results must match
|
|
55
|
-
exactly except for the recovery job UUID
|
|
55
|
+
exactly except for the recovery job UUID and the depth `package_sha256`.
|
|
56
|
+
That digest is provenance of the tarball this replay packed (README lives
|
|
57
|
+
inside the npm pack): the verifier requires `depth-package.json` beside the
|
|
58
|
+
ledger, hashes the artifact, checks filename/size/sha512 integrity, refuses
|
|
59
|
+
a missing or substituted pack, then compares behavior without requiring the
|
|
60
|
+
committed baseline digest (that digest is a labelled historical ledger
|
|
61
|
+
identity, not a re-hash of this run). A documentation-only
|
|
62
|
+
README change retargets the digest and must still reproduce every behavioral
|
|
63
|
+
verdict. The hostile comparison excludes
|
|
56
64
|
`generated_at` and per-scenario duration and canonicalizes only the two ratified
|
|
57
65
|
writer kinds of a cancelled terminal after checking the exact pairing. This proves that the
|
|
58
66
|
attested public release currently reproduces the committed behavioral claims.
|
|
@@ -86,12 +94,29 @@ Every release wave must ask and demonstrate an answer to these questions:
|
|
|
86
94
|
knowledge?
|
|
87
95
|
- Do ESM and CommonJS load from the packed tarball on every supported Node
|
|
88
96
|
major?
|
|
97
|
+
- Does a workflow the engine refuses reject native `run()` with the engine's
|
|
98
|
+
code and findings, on every refusal dialect a supported engine writes, with
|
|
99
|
+
no run handle, no second spawn and no preflight check? Does output that
|
|
100
|
+
proves neither admission nor refusal stay a protocol fault? The answer is
|
|
101
|
+
`test/native-run-admission.test.ts`. Its `wire-*` cases replay stdout and
|
|
102
|
+
stderr captured byte-for-byte from real engines
|
|
103
|
+
(`test/fixtures/run-wire/README.md` records which, and how); its
|
|
104
|
+
`SYNTHETIC` cases are invented hostile shapes. A replay proves the SDK
|
|
105
|
+
decodes those bytes, never that an engine still writes them: a new engine
|
|
106
|
+
release needs a new capture.
|
|
89
107
|
- What happens if the server dies after admission but before the first SSE
|
|
90
108
|
frame?
|
|
91
109
|
- What happens if SSE reconnects after a duplicate, gap, conflicting replay,
|
|
92
110
|
or terminal race?
|
|
93
111
|
- Can one slow observer overflow without damaging another observer or
|
|
94
|
-
`run.
|
|
112
|
+
`run.result()`?
|
|
113
|
+
- Does one application read the same lifecycle words over the native process
|
|
114
|
+
and over HTTP, with `event.raw` still the protocol frame, and without a
|
|
115
|
+
per-task event the resident never streamed?
|
|
116
|
+
- Does a frame whose state word is absent, null, future, or still running
|
|
117
|
+
stay an `engine.event` instead of being named settled?
|
|
118
|
+
- Is a human gate (`paused`) kept apart from both failure and completion, and
|
|
119
|
+
the engine's `interrupted` evidence apart from a broken observation?
|
|
95
120
|
- Can two clients race the same idempotency key with equal and unequal
|
|
96
121
|
snapshots?
|
|
97
122
|
- Does cancellation win or replay honestly when settlement races it?
|