@supernovae-st/nika 0.71.0 → 0.118.7
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/LICENSE +202 -0
- package/README.md +562 -30
- package/dist/bin/nika.js +407 -0
- package/dist/index.cjs +2889 -0
- package/dist/index.d.cts +593 -0
- package/dist/index.d.ts +593 -0
- package/dist/index.js +2838 -0
- package/docs/architecture.md +85 -0
- package/docs/http-api.md +87 -0
- package/docs/migrating-to-0.116.md +147 -0
- package/docs/testing.md +156 -0
- package/openapi.json +1 -0
- package/package.json +76 -24
- package/bin.js +0 -49
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# SDK architecture
|
|
2
|
+
|
|
3
|
+
The package has one public facade and two execution adapters:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
application
|
|
7
|
+
|
|
|
8
|
+
v
|
|
9
|
+
Nika facade
|
|
10
|
+
|
|
|
11
|
+
v
|
|
12
|
+
Transport interface <--- lifecycle and authority seam
|
|
13
|
+
| |
|
|
14
|
+
v v
|
|
15
|
+
NativeProcessTransport HttpTransport
|
|
16
|
+
| |
|
|
17
|
+
v v
|
|
18
|
+
local nika process authenticated nika serve
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Modules and responsibilities
|
|
22
|
+
|
|
23
|
+
- `src/index.ts` is the public Module. It validates caller-owned values, owns
|
|
24
|
+
run handles, and exposes one stable vocabulary.
|
|
25
|
+
- `src/lib/transport.ts` is the Interface. It describes the operations both
|
|
26
|
+
Adapters must implement and makes unsupported authority explicit.
|
|
27
|
+
- `src/lib/native-process-transport.ts` is the local Adapter. It spawns the
|
|
28
|
+
selected engine without a shell and consumes newline-delimited machine
|
|
29
|
+
events.
|
|
30
|
+
- `src/lib/http-transport.ts` is the remote Adapter. It verifies the remote
|
|
31
|
+
server identity once per client, resolves and verifies a local engine only
|
|
32
|
+
for caller-owned snapshot capture, then uses the authenticated HTTP
|
|
33
|
+
contract.
|
|
34
|
+
- `src/lib/run-session.ts` is the lifecycle Seam. It owns the eager event
|
|
35
|
+
pump, bounded independent observers, cancellation memoization, and the sole
|
|
36
|
+
terminal `run.done` settlement.
|
|
37
|
+
- `openapi.json` and `src/generated/openapi.d.ts` pin the HTTP contract judged
|
|
38
|
+
by CI. `scripts/check-sdk-coverage.js` fails if a live runtime path is
|
|
39
|
+
missing or if the SDK names a path outside that contract.
|
|
40
|
+
|
|
41
|
+
## Authority rules
|
|
42
|
+
|
|
43
|
+
The SDK transports engine facts; it does not reproduce engine decisions.
|
|
44
|
+
Parsing, admission, scheduling, cancellation settlement, receipts, trace
|
|
45
|
+
verification, permits, and cost remain engine-owned.
|
|
46
|
+
|
|
47
|
+
Some operations deliberately have one authority:
|
|
48
|
+
|
|
49
|
+
- resident workflow discovery, durable status, and schedules require HTTP;
|
|
50
|
+
- a direct native process refuses those operations with
|
|
51
|
+
`NikaCompatibilityError`;
|
|
52
|
+
- remote execution by contained workflow name uses the resident registry;
|
|
53
|
+
explicit local paths need a compatible local engine to capture a snapshot;
|
|
54
|
+
- when that capture is red the HTTP adapter returns the local engine's plain
|
|
55
|
+
`nika check --json` report, so `findings[]` stays canonical and no workflow
|
|
56
|
+
bytes are sent;
|
|
57
|
+
- HTTP observation (attach, durable status, events, cancel, workflow catalog,
|
|
58
|
+
schedule status, trace verdicts) needs no local engine;
|
|
59
|
+
- remote trace verification currently returns the engine's typed unavailable
|
|
60
|
+
verdict because the server has no path-free journal authority.
|
|
61
|
+
|
|
62
|
+
## Lifecycle invariants
|
|
63
|
+
|
|
64
|
+
1. `run()` resolves only after stable admission and returns an immutable
|
|
65
|
+
`{ id, done }` handle.
|
|
66
|
+
2. `run.done` is the only terminal promise. Workflow failure is result data;
|
|
67
|
+
configuration, transport, protocol, and compatibility failures throw.
|
|
68
|
+
3. `events(run)` creates an independent bounded observer. Aborting an observer
|
|
69
|
+
never cancels the run.
|
|
70
|
+
4. `cancel(run)` is idempotent per owned run handle.
|
|
71
|
+
5. `status(run)` reads the durable HTTP projection and refuses a native-only
|
|
72
|
+
run instead of guessing from local process state.
|
|
73
|
+
6. Schedule creation uses `If-None-Match: *`; updates require the exact opaque
|
|
74
|
+
revision in `If-Match`.
|
|
75
|
+
7. `attachRun()` creates a fresh owned session for an existing HTTP job; its
|
|
76
|
+
initial sequence is caller-owned durable checkpoint state, never inferred
|
|
77
|
+
from in-memory SDK history.
|
|
78
|
+
|
|
79
|
+
## Deletion test
|
|
80
|
+
|
|
81
|
+
If one Adapter is removed, the facade and lifecycle Seam remain coherent and
|
|
82
|
+
the other Adapter still compiles. If the Transport Interface is removed,
|
|
83
|
+
authority differences leak into every public method. If `run-session.ts` is
|
|
84
|
+
removed, each consumer must reimplement buffering, ownership, cancellation,
|
|
85
|
+
and settlement. Those boundaries therefore carry real architectural load.
|
package/docs/http-api.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# HTTP contract
|
|
2
|
+
|
|
3
|
+
`openapi.json` is the checked-in contract pin. The SDK authenticates every
|
|
4
|
+
route except public `GET /health`; bearer tokens are redacted from failures.
|
|
5
|
+
A non-2xx answer typed as `{ error: { code, message } }` becomes a
|
|
6
|
+
`NikaOperationError` carrying `status`, `code`, and the refused `operation`;
|
|
7
|
+
For a check by served name, a typed 404 or 422 instead returns
|
|
8
|
+
`{ clean: false, error }`; authentication and transport failures still throw.
|
|
9
|
+
Any other non-2xx body is discarded and reported as a redacted
|
|
10
|
+
`NikaTransportError`.
|
|
11
|
+
|
|
12
|
+
| HTTP route | SDK surface | Contract |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `GET /health` | internal identity handshake | public liveness and protocol versions |
|
|
15
|
+
| `GET /v1/openapi.json` | generation only | authenticated OpenAPI 3.1 document |
|
|
16
|
+
| `GET /v1/workflows` | `listWorkflows()` | contained relative workflow names |
|
|
17
|
+
| `GET /v1/workflows/{name}` | `workflow(name)` | path-free metadata, never source bytes |
|
|
18
|
+
| `POST /v1/check` | `check()` | validates a served name or immutable snapshot bytes without a job |
|
|
19
|
+
| `POST /v1/jobs` | `run()` | admits a served name or exact snapshot bytes with an idempotency key |
|
|
20
|
+
| `GET /v1/jobs/{id}` | internal settlement | durable job identity, outputs, receipt, settlement, or redacted error |
|
|
21
|
+
| `GET /v1/jobs/{id}/status` | `status(run)` | current status only |
|
|
22
|
+
| `GET /v1/jobs/{id}/events` | `events(run)` / `attachRun()` | bounded, sequenced SSE with replay |
|
|
23
|
+
| `POST /v1/jobs/{id}/cancel` | `cancel(run)` | 200 a settled job or its terminal replay; 202 the request accepted on a running job, settled later by observation |
|
|
24
|
+
| `GET /v1/jobs/{id}/trace/verify` | `traceVerify(receipt)` | engine-owned typed trace verdict; `reason` only on a verdict that does not hold |
|
|
25
|
+
| `GET/PUT /v1/schedules/{id}` | `scheduleStatus()` / `schedule()` | resident schedule projection and CAS mutation |
|
|
26
|
+
|
|
27
|
+
## Connection rules
|
|
28
|
+
|
|
29
|
+
- HTTPS is required for every host except loopback. Plain HTTP is accepted
|
|
30
|
+
only for `localhost`, `127.0.0.0/8`, or `[::1]`, and only with an explicit
|
|
31
|
+
`allowInsecureHttp: true`; that opt-in never admits a routable host.
|
|
32
|
+
- URLs containing credentials, a query, or a fragment are rejected.
|
|
33
|
+
- Tokens must contain 32–512 visible ASCII bytes and are never sent to
|
|
34
|
+
`/health`.
|
|
35
|
+
- Each request has a bounded timeout and each JSON/SSE machine frame has a
|
|
36
|
+
byte ceiling.
|
|
37
|
+
- Remote `check()` refuses `model` and `nativeStrict`; remote `run()` refuses
|
|
38
|
+
`vars`, `model`, and `maxCostUsd` until the request envelope owns them.
|
|
39
|
+
- Caller-provided workflow catalog names must be contained slash-separated
|
|
40
|
+
paths. Absolute paths, backslashes, empty segments, `.` and `..` are
|
|
41
|
+
rejected before network I/O.
|
|
42
|
+
|
|
43
|
+
A contained `.nika.yaml` name uses the resident registry without a local
|
|
44
|
+
engine. Prefix a local file with `./` to capture and submit its snapshot.
|
|
45
|
+
A successful by-name check returns `clean: true` and the compact resident
|
|
46
|
+
acknowledgement; no local check report or exit code is fabricated.
|
|
47
|
+
|
|
48
|
+
## Settlement
|
|
49
|
+
|
|
50
|
+
The terminal `execution.settled` frame and the durable job nest the run's
|
|
51
|
+
`settlement` whole (engine 0.118, ADR-128): its `status` and `cause`, the
|
|
52
|
+
elapsed time, the task tally, the spend with its qualifier, and the failure
|
|
53
|
+
named with its task. The SDK types every known field, refuses a settlement
|
|
54
|
+
whose `status` contradicts the record carrying it, keeps fields it does not
|
|
55
|
+
know, and never derives a settlement from an exit code; a job the resident
|
|
56
|
+
lost (`interrupted`) carries none.
|
|
57
|
+
|
|
58
|
+
## SSE recovery
|
|
59
|
+
|
|
60
|
+
The client checks that SSE ids are canonical positive integers and equal
|
|
61
|
+
`data.sequence`. An identical duplicate is ignored. A conflicting duplicate,
|
|
62
|
+
gap, or out-of-order frame is a protocol failure. After a reset the client
|
|
63
|
+
asks durable job state before reconnecting with `Last-Event-ID`; retry delays
|
|
64
|
+
and attempts are bounded.
|
|
65
|
+
|
|
66
|
+
A replacement Node process can call `attachRun(jobId, { lastEventId })`. The
|
|
67
|
+
SDK proves that the durable job exists before returning an owned run handle,
|
|
68
|
+
then sends the cursor as `Last-Event-ID`. Persist the job id and last event
|
|
69
|
+
sequence in the same application transaction that records each consumed event.
|
|
70
|
+
A cursor means “fully processed”, not merely “received”.
|
|
71
|
+
|
|
72
|
+
## Idempotency and schedules
|
|
73
|
+
|
|
74
|
+
An omitted run idempotency key is generated once per admission. A caller key
|
|
75
|
+
must be 1–255 bytes. Reusing a key with different snapshot bytes is an engine
|
|
76
|
+
conflict, not a retry success.
|
|
77
|
+
|
|
78
|
+
The namespace is the whole durable job store under the server's configured
|
|
79
|
+
`state-root`, across workflows, clients, schedules, and server restarts. The
|
|
80
|
+
current engine has no time-based eviction: keys remain bound while that state
|
|
81
|
+
root exists and still count toward its configured job capacity. Use globally
|
|
82
|
+
unique, business-stable keys; do not recycle daily counters or workflow-local
|
|
83
|
+
names.
|
|
84
|
+
|
|
85
|
+
Schedules use compare-and-swap semantics. Create omits `revision`; update must
|
|
86
|
+
carry the exact previous `sha256:...` revision. The SDK never fabricates or
|
|
87
|
+
normalizes schedule facts.
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Migrating to 0.116
|
|
2
|
+
|
|
3
|
+
Version 0.116 is an intentional breaking consolidation of the two published
|
|
4
|
+
0.115 clients into one `Nika` facade. This is a 0.x minor release, but existing
|
|
5
|
+
0.115 imports and method calls do not all remain source-compatible. Migrate in
|
|
6
|
+
a branch and run the packed-package gauntlets before upgrading production.
|
|
7
|
+
|
|
8
|
+
## Removed 0.115 surfaces
|
|
9
|
+
|
|
10
|
+
- The `@supernovae-st/nika-client/local` export and `LocalNika` class are
|
|
11
|
+
removed. Use `new Nika({ bin, cwd })`; call `check()`, `run()`, `events()`,
|
|
12
|
+
and `traceVerify()` on that instance.
|
|
13
|
+
- The root `nika.jobs` and `nika.workflows` namespaces are removed. Use the
|
|
14
|
+
facade methods shown below.
|
|
15
|
+
- `Nika.fromEnv()`, `nika.health()`, `Nika.verifyWebhook()`, and the exported
|
|
16
|
+
webhook helper are removed. Construct the client explicitly; health is now
|
|
17
|
+
an internal compatibility preflight. Keep webhook verification in the
|
|
18
|
+
application boundary that owns its signing format.
|
|
19
|
+
- Preview artifact, workflow-source/reload, and `runAndCollect` helpers are
|
|
20
|
+
removed instead of continuing as methods that always refuse.
|
|
21
|
+
- Node 18 and 20 are no longer supported; the package now requires Node 22 or
|
|
22
|
+
newer.
|
|
23
|
+
|
|
24
|
+
## Constructor migration
|
|
25
|
+
|
|
26
|
+
The 0.115 root constructor was HTTP-only. In 0.116, no URL means the native
|
|
27
|
+
process transport; supplying `url` and `token` selects HTTP. Remote `check()`
|
|
28
|
+
and `run()` also need a local Nika binary (`bin`, `NIKA_BIN`, or the exact
|
|
29
|
+
optional host payload package) because
|
|
30
|
+
the SDK captures and validates immutable snapshot bytes before admission.
|
|
31
|
+
The current by-name HTTP path also accepts contained workflow names without a
|
|
32
|
+
local engine; prefix a local file with `./` to retain snapshot capture.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// 0.115
|
|
36
|
+
const oldClient = new Nika({ url, token, timeout: 30_000 });
|
|
37
|
+
|
|
38
|
+
// 0.116
|
|
39
|
+
const nika = new Nika({
|
|
40
|
+
url,
|
|
41
|
+
token,
|
|
42
|
+
bin: process.env.NIKA_BIN,
|
|
43
|
+
requestTimeout: 30_000,
|
|
44
|
+
allowInsecureHttp: url.startsWith('http://127.0.0.1'),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`timeout`, retries, polling, concurrency, logger, and per-run `signal` options
|
|
49
|
+
from the old HTTP client are gone. Request and frame bounds are client
|
|
50
|
+
invariants; observe or cancel a returned run through its owned lifecycle.
|
|
51
|
+
|
|
52
|
+
## Method mapping
|
|
53
|
+
|
|
54
|
+
| 0.115 | 0.116 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `local.check(file)` | `nika.check(file)` |
|
|
57
|
+
| `local.run(file)` | `await nika.run(file)`, then `nika.events(run)` and `run.done` |
|
|
58
|
+
| `local.runToEnd(file)` | `const run = await nika.run(file); await run.done` |
|
|
59
|
+
| `local.traceVerify(path)` | No path-based replacement; retain the engine-issued receipt and call `nika.traceVerify(receipt)` |
|
|
60
|
+
| `nika.jobs.submit(workflow)` | `nika.run(workflow)` |
|
|
61
|
+
| `nika.jobs.status(id)` | retain the owned run; `nika.status(run)` |
|
|
62
|
+
| `nika.jobs.stream(id)` | `attachRun(id)`, then `events(run)` |
|
|
63
|
+
| `nika.jobs.cancel(id)` | `nika.cancel(run)` |
|
|
64
|
+
| `nika.workflows.list()` | `nika.listWorkflows()` |
|
|
65
|
+
| `nika.workflows.metadata(name)` | `nika.workflow(name)` |
|
|
66
|
+
|
|
67
|
+
The new run handle is intentionally only `{ id, done }`. Methods reject a
|
|
68
|
+
look-alike object from another client, so persist the job id and reattach after
|
|
69
|
+
a process restart instead of rebuilding a handle by hand.
|
|
70
|
+
|
|
71
|
+
`LocalNika.version()`, `dryRunPlan()`, and `test()` have no One SDK method in
|
|
72
|
+
0.116. Keep those CLI-facing probes in deployment/CI (`nika --version`,
|
|
73
|
+
`nika run --dry-run --json`, and `nika test`) until a future typed authority is
|
|
74
|
+
explicitly admitted. This release does not silently emulate them.
|
|
75
|
+
|
|
76
|
+
## The check report changed shape
|
|
77
|
+
|
|
78
|
+
`check()` still returns `clean` and `exitCode`, but the object around them is
|
|
79
|
+
no longer the 0.115 `LocalCheckReport` (eleven curated fields plus a `raw`
|
|
80
|
+
escape hatch). It is the engine's own machine report, passed through: the
|
|
81
|
+
former `raw` contents are now the top level, `reportVersion` is
|
|
82
|
+
`report_version`, and `parseFatal` and `warnings` are gone. The engine emits
|
|
83
|
+
its identity under both casings (`engineVersion` and `engine_version`,
|
|
84
|
+
`buildSha` and `build_sha`, `specSha` and `spec_sha`, `checkReportVersion`
|
|
85
|
+
and `report_version`); read either, do not diff them. In TypeScript only
|
|
86
|
+
`report_version`, `clean`, and `exitCode` are typed; every other field,
|
|
87
|
+
including `cost`, `findings`, and `hints`, is `unknown` behind an index
|
|
88
|
+
signature, so strict callers narrow it themselves. Two problems that look like
|
|
89
|
+
findings (a missing `permits:` block, a missing `max_tokens:`) are reported
|
|
90
|
+
under `hints[]` (`{ kind, task, advice }`, no `code`), not `findings[]`, in
|
|
91
|
+
both versions.
|
|
92
|
+
|
|
93
|
+
`runToEnd()` returned `{ ok, exitCode, events[] }` with every event buffered.
|
|
94
|
+
`run.done` returns the terminal result only; the events ride `events(run)`
|
|
95
|
+
as a bounded live iterator (default 256), so observe it concurrently or the
|
|
96
|
+
prefix is gone. The removed methods (`version()`, `dryRunPlan()`, path-based
|
|
97
|
+
`traceVerify()`) are absent, not stubbed: calling them throws a plain
|
|
98
|
+
`TypeError: … is not a function`, and a trace that only exists as a file
|
|
99
|
+
path in a later process has no SDK verification door in 0.116.
|
|
100
|
+
|
|
101
|
+
## New resident discovery
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const names = await nika.listWorkflows();
|
|
105
|
+
const metadata = await nika.workflow(names[0]);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
These methods require an HTTP client. A native-process client returns a typed
|
|
109
|
+
`NikaCompatibilityError` with capability `workflowCatalog`.
|
|
110
|
+
|
|
111
|
+
## Durable status
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const run = await nika.run('flow.nika.yaml');
|
|
115
|
+
console.log(await nika.status(run));
|
|
116
|
+
console.log(await run.done);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`status(run)` is an observation, not terminal settlement. Keep `run.done` as
|
|
120
|
+
the sole terminal promise. Native-process runs refuse `status()` because a
|
|
121
|
+
short-lived process has no independent durable status authority.
|
|
122
|
+
|
|
123
|
+
## Durable run recovery
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
const recovered = await nika.attachRun(saved.jobId, {
|
|
127
|
+
lastEventId: saved.lastEventSequence,
|
|
128
|
+
});
|
|
129
|
+
for await (const event of nika.events(recovered)) {
|
|
130
|
+
await saveApplicationCheckpoint(recovered.id, event.sequence);
|
|
131
|
+
}
|
|
132
|
+
console.log(await recovered.done);
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`attachRun()` is HTTP-only. It proves the job exists, returns a normal owned
|
|
136
|
+
run handle, and resumes the stream with `Last-Event-ID`. Save the job id and
|
|
137
|
+
event cursor in application durable state before the original process exits.
|
|
138
|
+
If more events arrive before the application subscribes than its configured
|
|
139
|
+
buffer can retain, `events()` refuses with `NikaEventBufferOverflowError`
|
|
140
|
+
instead of silently skipping a replay prefix.
|
|
141
|
+
|
|
142
|
+
## Contract and release alignment
|
|
143
|
+
|
|
144
|
+
Version 0.116 targets the engine train whose OpenAPI contract contains check,
|
|
145
|
+
jobs, status, events, cancellation, typed trace verification, resident
|
|
146
|
+
workflow discovery, and schedule CAS. Do not publish the SDK before the
|
|
147
|
+
matching engine release and native payload assets exist.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Testing and release evidence
|
|
2
|
+
|
|
3
|
+
The release judge is the packed package consumed from an isolated Node
|
|
4
|
+
project, not a source import. Local development still starts with the fast
|
|
5
|
+
gates:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm ci
|
|
9
|
+
npm test
|
|
10
|
+
npm run build
|
|
11
|
+
npm run check:coverage
|
|
12
|
+
npm run check:release-evidence
|
|
13
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:check
|
|
14
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:run
|
|
15
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:projects
|
|
16
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:depth
|
|
17
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:hostile
|
|
18
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:recovery
|
|
19
|
+
NIKA_BIN=/path/to/nika npm run gauntlet:one-door
|
|
20
|
+
npm audit
|
|
21
|
+
npm pack --dry-run
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
All engine-backed gauntlets use `NIKA_BIN` as the canonical explicit binary.
|
|
25
|
+
`NIKA_GAUNTLET_BIN` remains a compatibility fallback for the corpus-only
|
|
26
|
+
scripts. Evidence is invalid when the recorded engine identity does not match
|
|
27
|
+
the intended release candidate. `npm run check:release-evidence` binds every
|
|
28
|
+
current committed gauntlet result and packed tarball identity to the root
|
|
29
|
+
package version. Historical ledgers are limited to an explicit allowlist and
|
|
30
|
+
must remain labelled as non-gating evidence.
|
|
31
|
+
|
|
32
|
+
CI adds a behavioral provenance replay. It downloads the Linux x64 asset for
|
|
33
|
+
the exact root package version, verifies its GitHub attestation and published
|
|
34
|
+
`SHA256SUMS` entry, then reruns all 100 deterministic workflows, the hostile suite, all five mini-SaaS projects, all five depth projects,
|
|
35
|
+
the two-process recovery scenario, and five scenarios through six execution
|
|
36
|
+
doors from a freshly packed SDK. The runner
|
|
37
|
+
mints an ephemeral run-signing key. Its
|
|
38
|
+
cancellation fixtures retain the in-process `nika:wait` cases and add an
|
|
39
|
+
owned loopback rendezvous for controlled task-boundary cancellation. They
|
|
40
|
+
need no shell command, platform sandbox, or sandbox waiver. Cancellation
|
|
41
|
+
and sealed-trace claims are exercised against the public binary. The
|
|
42
|
+
cancellation fixtures cancel an execution that is observably inside its 10 s
|
|
43
|
+
`nika:wait` (the durable status reads `running`, no longer `queued`, and a
|
|
44
|
+
further delay has passed) and record both the cancel reply and the terminal it
|
|
45
|
+
leads to. On engine 0.118 the resident answers 202 `cancellation_requested`;
|
|
46
|
+
its execution owner then records `execution.interrupted` with
|
|
47
|
+
`status=interrupted` once the grace expires inside a task, or, when the request
|
|
48
|
+
lands at a task boundary, `execution.cancelled` (the `cancel_job` writer) or
|
|
49
|
+
`execution.settled` (the racing settlement writer) with `status=cancelled` and a
|
|
50
|
+
settlement whose cause is `operator`. A cancel that lands before the execution
|
|
51
|
+
starts is a 200 `cancelled` whose terminal is one of those two writer kinds,
|
|
52
|
+
only with `status=cancelled`. The verifiers bind each cancel reply to the
|
|
53
|
+
terminals it may lead to, demand the run status of that terminal, and refuse any
|
|
54
|
+
other pairing. The parsed deterministic and packed-project results must match
|
|
55
|
+
exactly except for the recovery job UUID. The hostile comparison excludes
|
|
56
|
+
`generated_at` and per-scenario duration and canonicalizes only the two ratified
|
|
57
|
+
writer kinds of a cancelled terminal after checking the exact pairing. This proves that the
|
|
58
|
+
attested public release currently reproduces the committed behavioral claims.
|
|
59
|
+
It does not claim cryptographic proof of when the committed JSON file itself
|
|
60
|
+
was originally written.
|
|
61
|
+
|
|
62
|
+
## Test layers
|
|
63
|
+
|
|
64
|
+
1. Unit tests cover configuration, local process framing, HTTP protocol
|
|
65
|
+
validation, SSE recovery, independent observer backpressure, scheduling,
|
|
66
|
+
receipts, and typed errors.
|
|
67
|
+
2. The generated corpus holds 100 distinct use cases and 100 valid workflows.
|
|
68
|
+
3. The deterministic runner executes every workflow with `mock/echo` and
|
|
69
|
+
seals trace evidence without paid-provider dependence.
|
|
70
|
+
4. Project gauntlets install the tarball into fresh applications and exercise
|
|
71
|
+
realistic multi-step use cases.
|
|
72
|
+
5. Hostile tests mutate transport frames, timing, status codes, identities,
|
|
73
|
+
revisions, and replay order.
|
|
74
|
+
6. Public Personas use only the README, exported types, packed package, public
|
|
75
|
+
binary/help, loopback HTTP, and public documentation. They are synthetic
|
|
76
|
+
users, never substitutes for human usability evidence.
|
|
77
|
+
|
|
78
|
+
The latest public-only first-contact wave and its convergent debt are recorded
|
|
79
|
+
in [`gauntlet/personas/REPORT.md`](../gauntlet/personas/REPORT.md).
|
|
80
|
+
|
|
81
|
+
## Socratic risk matrix
|
|
82
|
+
|
|
83
|
+
Every release wave must ask and demonstrate an answer to these questions:
|
|
84
|
+
|
|
85
|
+
- Can a first-time Node user succeed from the README without repository
|
|
86
|
+
knowledge?
|
|
87
|
+
- Do ESM and CommonJS load from the packed tarball on every supported Node
|
|
88
|
+
major?
|
|
89
|
+
- What happens if the server dies after admission but before the first SSE
|
|
90
|
+
frame?
|
|
91
|
+
- What happens if SSE reconnects after a duplicate, gap, conflicting replay,
|
|
92
|
+
or terminal race?
|
|
93
|
+
- Can one slow observer overflow without damaging another observer or
|
|
94
|
+
`run.done`?
|
|
95
|
+
- Can two clients race the same idempotency key with equal and unequal
|
|
96
|
+
snapshots?
|
|
97
|
+
- Does cancellation win or replay honestly when settlement races it?
|
|
98
|
+
- Does a stale schedule writer receive the current revision without mutating
|
|
99
|
+
durable state?
|
|
100
|
+
- Do process and server restarts preserve the facts the API claims are
|
|
101
|
+
durable?
|
|
102
|
+
- Can a replacement client reattach with its last committed SSE cursor without
|
|
103
|
+
replaying an application side effect?
|
|
104
|
+
- Are auth failures, token rotation, malformed content types, compressed
|
|
105
|
+
bodies, oversized frames, invalid UTF-8, and timeouts typed and redacted?
|
|
106
|
+
- Are receipt job, execution, and trace identities consistent across SSE,
|
|
107
|
+
durable state, and verification?
|
|
108
|
+
- Is the run-signing private key still confined to engine custody, with only
|
|
109
|
+
public trust material entering application infrastructure?
|
|
110
|
+
- Does every live OpenAPI route have a deliberate SDK treatment?
|
|
111
|
+
- Does every documented example compile and run from the tarball?
|
|
112
|
+
- Does the version agree across package metadata, lockfile, optional native
|
|
113
|
+
packages, OpenAPI identity, engine release, npm, and GitHub?
|
|
114
|
+
- Can a claimed capability be deleted without a gate becoming red? If yes,
|
|
115
|
+
the capability is not yet wired.
|
|
116
|
+
|
|
117
|
+
## Release evidence
|
|
118
|
+
|
|
119
|
+
Record exact commands, versions, commit SHAs, platform, run counts, cost, and
|
|
120
|
+
the path to machine-readable results. A green unit suite alone is never release
|
|
121
|
+
evidence. A failed or skipped lane stays named; it is not rounded into a pass.
|
|
122
|
+
|
|
123
|
+
The release ceremony is deliberately two-step. `release.yml` validates the
|
|
124
|
+
tagged engine assets, starts the released Linux binary, proves the live
|
|
125
|
+
OpenAPI/types pin, embeds the exact prepared commit and release version in all
|
|
126
|
+
five package manifests before packing, and publishes four payloads plus the SDK
|
|
127
|
+
through npm trusted publishing with GitHub OIDC and Sigstore provenance bound
|
|
128
|
+
to the workflow identity. Every package registers organization `supernovae-st`,
|
|
129
|
+
repository `nika-client`, workflow filename `release.yml`, and environment
|
|
130
|
+
`npm-publish`, with direct `npm publish` enabled. The GitHub-hosted publish job
|
|
131
|
+
uses Node 24, npm 11.19.1 and `id-token: write`; it receives no npm write token.
|
|
132
|
+
`release-heal.yml` dispatches that same file on `main`; it does not publish or
|
|
133
|
+
exchange an OIDC token itself. All five package manifests identify the SDK
|
|
134
|
+
repository; native `SOURCE.json` still identifies the separate engine source.
|
|
135
|
+
See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
136
|
+
An occupied version is accepted only after its exact
|
|
137
|
+
prepared tarball integrity and fetched registry bytes match; errors other than
|
|
138
|
+
an explicit registry 404 refuse publication. `release-finalize.yml` refuses to create the SDK tag and GitHub
|
|
139
|
+
Release until all five exact versions are publicly observable on npm and every
|
|
140
|
+
published manifest carries the same prepared commit and version.
|
|
141
|
+
|
|
142
|
+
## One-door parity and process supervision
|
|
143
|
+
|
|
144
|
+
`gauntlet:one-door` compares CLI, raw HTTP by name and snapshot, and packed SDK
|
|
145
|
+
native, by-name and snapshot execution. It checks success, failure, recovery,
|
|
146
|
+
paused observation and controlled cancellation. `NIKA_ONE_DOOR_REPORT` names
|
|
147
|
+
its output file; CI retains it alongside the replay results. Development mode
|
|
148
|
+
uses an offline installation of the freshly packed SDK with the explicit
|
|
149
|
+
`NIKA_BIN`. Public npm parity requires `NIKA_PUBLIC_SDK_VERSION` and an outer
|
|
150
|
+
artifact-provenance gate; runtime agreement alone is not an attestation.
|
|
151
|
+
|
|
152
|
+
All harnesses own their child processes, impose finite deadlines and await
|
|
153
|
+
cleanup before emitting green evidence. The corpus runs in a fresh project and
|
|
154
|
+
HOME. Changed fixtures require new measured results: old committed ledgers
|
|
155
|
+
remain historical observations until a successful exact-version replay replaces
|
|
156
|
+
them. Never relabel an old binary or weaken the replay comparison.
|
package/openapi.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"components":{"parameters":{"IdempotencyKey":{"in":"header","name":"Idempotency-Key","required":true,"schema":{"maxLength":255,"minLength":1,"type":"string"}},"IfMatch":{"in":"header","name":"If-Match","required":false,"schema":{"maxLength":96,"type":"string"}},"IfNoneMatch":{"in":"header","name":"If-None-Match","required":false,"schema":{"const":"*","type":"string"}},"LastEventId":{"in":"header","name":"Last-Event-ID","required":false,"schema":{"pattern":"^(0|[1-9][0-9]*)$","type":"string"}}},"schemas":{"Error":{"additionalProperties":false,"properties":{"error":{"additionalProperties":false,"properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"}},"required":["error"],"type":"object"},"ExecutionSnapshot":{"additionalProperties":false,"description":"Immutable byte-owned execution world — the body `nika check <file> --json --sdk-snapshot` prints (the engine is the one producer; a client never hashes). Unit bytes are canonical lowercase hexadecimal. `digest` and every unit `digest` are OPTIONAL caller-supplied integrity digests (canonical lowercase SHA-256 · a content assertion, never a signature): absent, the resident computes them and the receipt carries the result; present, they must match the bytes or the request is refused as `snapshot_tampered`. The decoded unit aggregate is limited to 16 MiB and the complete encoded request to 33 MiB. This object is the request body itself, not a path-bearing wrapper.","properties":{"digest":{"description":"Optional caller-supplied integrity digest of the world (never a signature)","pattern":"^[0-9a-f]{64}$","type":"string"},"format_version":{"const":1,"type":"integer"},"root":{"maxLength":4096,"minLength":1,"type":"string"},"units":{"items":{"additionalProperties":false,"properties":{"bytes_hex":{"pattern":"^(?:[0-9a-f]{2})*$","type":"string"},"digest":{"description":"Optional caller-supplied integrity digest of the unit (never a signature)","pattern":"^[0-9a-f]{64}$","type":"string"},"kind":{"description":"0 root (the admitted workflow) · 1 child (a transitively invoked workflow) · 2 skill (an Agent Skill document) · 3 import (an opaque import the caller supplied)","maximum":3,"minimum":0,"type":"integer"},"path":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["path","kind","bytes_hex"],"type":"object"},"maxItems":256,"type":"array"}},"required":["format_version","root","units"],"type":"object"},"Health":{"additionalProperties":false,"properties":{"api_version":{"minLength":1,"type":"string"},"buildSha":{"minLength":1,"type":"string"},"build_sha":{"minLength":1,"type":"string"},"checkReportVersion":{"minimum":1,"type":"integer"},"engineVersion":{"minLength":1,"type":"string"},"engine_version":{"minLength":1,"type":"string"},"eventFormatVersion":{"minimum":1,"type":"integer"},"machineProtocolVersion":{"minimum":1,"type":"integer"},"service":{"const":"nika-serve","type":"string"},"snapshotFormatVersion":{"minimum":1,"type":"integer"},"specSha":{"minLength":1,"type":"string"},"spec_sha":{"minLength":1,"type":"string"},"status":{"const":"ok","type":"string"},"supportedCapabilities":{"items":{"type":"string"},"type":"array","uniqueItems":true},"traceFormatVersion":{"minimum":1,"type":"integer"}},"required":["status","service","engine_version","build_sha","spec_sha","api_version","engineVersion","buildSha","specSha","machineProtocolVersion","snapshotFormatVersion","checkReportVersion","eventFormatVersion","traceFormatVersion","supportedCapabilities"],"type":"object"},"Job":{"additionalProperties":false,"properties":{"error":{"additionalProperties":false,"properties":{"code":{"type":"string"},"message":{"type":"string"}},"required":["code","message"],"type":"object"},"execution_id":{"type":"string"},"id":{"format":"uuid","type":"string"},"outputs":{"additionalProperties":true,"description":"Declared workflow outputs; present only after settlement when supplied by the execution adapter","type":"object"},"receipt":{"$ref":"#/components/schemas/JobReceipt"},"settlement":{"$ref":"#/components/schemas/RunSettlement"},"status":{"$ref":"#/components/schemas/JobStatus"},"trace_id":{"type":"string"}},"required":["id","status"],"type":"object"},"JobByName":{"additionalProperties":false,"description":"The by-name form (ADR-131): a workflow the served registry lists (GET /v1/workflows · project-root-relative, `.nika.yaml`). The resident captures its world exactly as a schedule does — the one owner of the snapshot and its digest domain. Idempotency binds to these request bytes.","properties":{"workflow":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["workflow"],"type":"object"},"JobEvent":{"additionalProperties":false,"properties":{"code":{"type":"string"},"kind":{"type":["string","null"]},"message":{"type":"string"},"outputs":{"additionalProperties":true,"type":"object"},"receipt":{"$ref":"#/components/schemas/JobReceipt"},"sequence":{"minimum":1,"type":"integer"},"settlement":{"$ref":"#/components/schemas/RunSettlement"},"status":{"anyOf":[{"$ref":"#/components/schemas/JobStatus"},{"type":"null"}]}},"required":["sequence","kind","status"],"type":"object"},"JobOrigin":{"oneOf":[{"additionalProperties":false,"properties":{"kind":{"const":"manual","type":"string"}},"required":["kind"],"type":"object"},{"additionalProperties":false,"properties":{"arm_generation":{"pattern":"^[0-9a-f]{64}$","type":"string"},"decision":{"enum":["scheduled","catch_up"],"type":"string"},"fired_at":{"format":"date-time","type":"string"},"kind":{"const":"schedule","type":"string"},"schedule_id":{"description":"Origin-local identifier, bounded to 255 UTF-8 bytes by the server","maxLength":255,"minLength":1,"type":"string"},"schedule_origin":{"enum":["project","api"],"type":"string"},"schedule_revision":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"scheduled_for":{"format":"date-time","type":"string"},"slot_id":{"pattern":"^[0-9a-f]{64}$","type":"string"}},"required":["kind","schedule_origin","schedule_id","schedule_revision","slot_id","decision","scheduled_for","fired_at","arm_generation"],"type":"object"}]},"JobReceipt":{"additionalProperties":false,"description":"Terminal binding to the exact immutable admitted execution","properties":{"chain_head":{"minLength":1,"type":"string"},"execution_id":{"minLength":1,"type":"string"},"job_id":{"format":"uuid","type":"string"},"origin":{"$ref":"#/components/schemas/JobOrigin"},"snapshot_digest":{"pattern":"^[0-9a-f]{64}$","type":"string"},"trace_id":{"minLength":1,"type":"string"}},"required":["job_id","execution_id","trace_id","snapshot_digest"],"type":"object"},"JobStatus":{"description":"queued and running: the resident owns the execution. interrupted: execution ownership was lost and effect settlement is unknown — an EVIDENCE state (the journal is INCOMPLETE), never a run state (ADR-129). paused, succeeded, failed and cancelled: the run's own settlement, the words its terminal frame carries (ADR-128).","enum":["queued","running","interrupted","paused","succeeded","failed","cancelled"],"type":"string"},"JobStatusOnly":{"additionalProperties":false,"description":"Status only. Redacted diagnosis lives on GET /v1/jobs/{id} and SSE, never here.","properties":{"status":{"$ref":"#/components/schemas/JobStatus"}},"required":["status"],"type":"object"},"RunSettlement":{"additionalProperties":false,"description":"The run's settlement (ADR-128), built once by the runtime and projected whole: the state word every door speaks, why, the elapsed time on the kernel clock, the task tally, the spend with its qualifier, the failure named. Unknown cost is never zero: `total_cost_usd` is absent when nothing was metered. Present on the terminal event and durable job response of a job whose runtime settled; absent when the resident lost the execution (interrupted) or refused it before any task. Reattachment and idempotent admission replay project the same hash-bound terminal event, never a new settlement.","properties":{"cause":{"enum":["normal","human_gate","task_failed","output_contract","budget","operator","refused"],"type":"string"},"elapsed_ms":{"minimum":0,"type":"integer"},"error":{"additionalProperties":false,"properties":{"code":{"type":"string"},"message":{"type":"string"},"task":{"description":"The task that failed · absent for a run-level cause","type":"string"}},"required":["code","message"],"type":"object"},"spend":{"additionalProperties":false,"properties":{"by_source":{"additionalProperties":{"type":"number"},"type":"object"},"priced_calls":{"minimum":0,"type":"integer"},"pricing_as_of":{"type":"string"},"qualifier":{"enum":["priced","partially_priced","unpriced","unmetered"],"type":"string"},"total_cost_usd":{"description":"Present only when at least one leaf metered real spend","minimum":0,"type":"number"},"unpriced_calls":{"minimum":0,"type":"integer"}},"required":["priced_calls","unpriced_calls","qualifier"],"type":"object"},"status":{"enum":["succeeded","failed","paused","cancelled"],"type":"string"},"tasks":{"additionalProperties":false,"properties":{"cancelled":{"minimum":0,"type":"integer"},"failed":{"minimum":0,"type":"integer"},"never_started":{"description":"Cancelled at the boundary without ever starting (counted in `cancelled` too)","minimum":0,"type":"integer"},"ok":{"description":"A recovered task IS a success (counted here too)","minimum":0,"type":"integer"},"recovered":{"minimum":0,"type":"integer"},"skipped":{"minimum":0,"type":"integer"},"total":{"minimum":0,"type":"integer"}},"required":["total","ok","failed","recovered","skipped","cancelled","never_started"],"type":"object"}},"required":["status","cause","spend"],"type":"object"},"ScheduleApply":{"additionalProperties":false,"properties":{"applied":{"const":true,"type":"boolean"},"changed":{"type":"boolean"},"status":{"$ref":"#/components/schemas/ScheduleStatus"}},"required":["applied","changed","status"],"type":"object"},"SchedulePut":{"additionalProperties":false,"properties":{"active":{"type":"boolean"},"afterSkip":{"enum":["next_slot","on_completion"],"type":"string"},"jitter":{"enum":["hash"],"type":"string"},"maxCostUsd":{"exclusiveMinimum":0,"type":"number"},"maxLatenessSeconds":{"minimum":0,"type":"integer"},"missed":{"enum":["catch-up","catch-up-once","skip"],"type":"string"},"overlap":{"enum":["skip","queue","replace"],"type":"string"},"pauseReason":{"maxLength":1024,"type":"string"},"pauseUntil":{"format":"date","type":"string"},"tolerance":{"type":"string"},"when":{"oneOf":[{"additionalProperties":false,"properties":{"at":{"format":"date-time","type":"string"},"kind":{"const":"once"}},"required":["kind","at"],"type":"object"},{"additionalProperties":false,"properties":{"expression":{"maxLength":4096,"type":"string"},"kind":{"const":"cadence"}},"required":["kind","expression"],"type":"object"}]},"workflow":{"maxLength":1024,"pattern":"^[^/].*\\.nika\\.yaml$","type":"string"}},"required":["workflow","when","maxCostUsd","missed"],"type":"object"},"ScheduleStatus":{"additionalProperties":true,"description":"Normalized definition, origin, distinct schedule revision, active/pause state, due verdict, bounded next slots with shift evidence, earliest wake hint, and last durable decision.","type":"object"},"SnapshotValidationAck":{"additionalProperties":false,"description":"Compact remote acknowledgement that the exact snapshot was revalidated. This is not the engine's public full check report; SDK callers retain the engine-owned report captured with the snapshot and return it only after this acknowledgement succeeds.","properties":{"root":{"minLength":1,"type":"string"},"snapshot_digest":{"pattern":"^[0-9a-f]{64}$","type":"string"},"status":{"const":"accepted","type":"string"},"units":{"minimum":1,"type":"integer"}},"required":["status","snapshot_digest","root","units"],"type":"object"},"TraceVerification":{"additionalProperties":false,"description":"Run-scoped typed verdict. Unavailable is an honest refusal: this server has no remote trace-journal authority and never scans or returns filesystem paths.","properties":{"reason":{"enum":["run_not_terminal","trace_journal_unavailable"],"type":"string"},"trace_id":{"minLength":1,"type":"string"},"verdict":{"enum":["unavailable"],"type":"string"}},"required":["verdict","reason"],"type":"object"},"WorkflowList":{"additionalProperties":false,"properties":{"workflows":{"items":{"minLength":1,"type":"string"},"type":"array"}},"required":["workflows"],"type":"object"},"WorkflowMetadata":{"additionalProperties":false,"properties":{"workflow":{"minLength":1,"type":"string"}},"required":["workflow"],"type":"object"}},"securitySchemes":{"bearerAuth":{"description":"Exactly one Authorization: Bearer value from the token file","scheme":"bearer","type":"http"}}},"info":{"description":"Authenticated loopback remote execution and declarative schedules. Artifacts, schedule list/delete/trigger/backfill, /v1/arm, and POST /v1/run are absent.","title":"nika serve","version":"0.118.7"},"openapi":"3.1.0","paths":{"/health":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}},"description":"Engine identity only"}},"security":[],"summary":"Public process liveness"}},"/v1/check":{"post":{"description":"Runs the same admission as POST /v1/jobs (ADR-131 · both forms) over the exact request body, and creates nothing.","requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/JobByName"},{"$ref":"#/components/schemas/ExecutionSnapshot"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotValidationAck"}}},"description":"Compact snapshot validation acknowledgement, not the full engine check report"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"408":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Request deadline"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Encoded body or decoded snapshot resource limit"},"415":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Content-Type or Content-Encoding refused"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Malformed, unsupported, tampered, or semantically refused snapshot"}},"summary":"Judge a workflow without creating a job — by served name, or as immutable snapshot bytes"}},"/v1/jobs":{"post":{"description":"Two forms, one admission (ADR-131). `{\"workflow\": \"<name>\"}` names a workflow the served registry lists: the resident captures its world through ExecutionService, exactly as a schedule does. A snapshot body is the world `nika check <file> --json --sdk-snapshot` prints, decoded and readmitted through the same ExecutionService; its digests are optional caller-supplied integrity digests (a content assertion, never a signature). The server never interprets a caller filesystem path. Idempotency binds to the exact request bytes.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/JobByName"},{"$ref":"#/components/schemas/ExecutionSnapshot"}]}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"description":"Idempotent replay"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"description":"Created"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid idempotency key"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"408":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Request deadline"},"409":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Idempotency key already bound to another request"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Encoded body or decoded snapshot resource limit"},"415":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Content-Type or Content-Encoding refused"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Malformed, unsupported, tampered, or semantically refused snapshot"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Execution queue or durable store unavailable"},"507":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Durable job capacity exhausted"}},"summary":"Admit a workflow as a durable job — by served name, or as immutable snapshot bytes"}},"/v1/jobs/{id}":{"get":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"description":"Job"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Job identity and status"}},"/v1/jobs/{id}/cancel":{"post":{"description":"A queued job cancels atomically before execution claims it. An active job receives the run-scoped cancellation signal and returns 202 until its execution owner settles: the result may be success, failure or cancellation; expired grace means interrupted, never an invented cancellation. A paused or final observation returns its existing result unchanged.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"description":"Cancelled before execution, or already ended observation"},"202":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}},"description":"Cancellation requested; execution has not yet settled"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Request cancellation or replay an ended observation"}},"/v1/jobs/{id}/events":{"get":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}},{"$ref":"#/components/parameters/LastEventId"}],"responses":{"200":{"content":{"text/event-stream":{"schema":{"type":"string"},"x-nika-event-schema":{"$ref":"#/components/schemas/JobEvent"}}},"description":"text/event-stream; sends bounded retry guidance and cursor-neutral heartbeat comments; Last-Event-ID replays only persisted events after that sequence; terminal data adds declared outputs and receipt when available; failures add redacted {code,message}"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Job event SSE"}},"/v1/jobs/{id}/status":{"get":{"parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusOnly"}}},"description":"Status"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Status only; diagnosis lives on GET /v1/jobs/{id} and SSE"}},"/v1/jobs/{id}/trace/verify":{"get":{"description":"Returns a typed honest refusal while no remote trace-journal authority exists. It never scans a trace directory or exposes a filesystem path.","parameters":[{"in":"path","name":"id","required":true,"schema":{"format":"uuid","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TraceVerification"}}},"description":"Typed trace verdict"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Return the run-scoped trace verification verdict"}},"/v1/openapi.json":{"get":{"responses":{"200":{"description":"OpenAPI 3.1"},"401":{"description":"Bearer required"}},"summary":"This document"}},"/v1/schedules/{id}":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleStatus"}}},"description":"Fresh planned status","headers":{"ETag":{"schema":{"type":"string"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Read one declarative resident schedule"},"parameters":[{"in":"path","name":"id","required":true,"schema":{"maxLength":255,"minLength":1,"type":"string"}}],"put":{"description":"Create requires If-None-Match: *. Update requires the exact ETag in If-Match. Identical lost-response retries are unchanged and retain the revision.","parameters":[{"$ref":"#/components/parameters/IfNoneMatch"},{"$ref":"#/components/parameters/IfMatch"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SchedulePut"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScheduleApply"}}},"description":"Applied or unchanged","headers":{"ETag":{"schema":{"type":"string"}}}},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"412":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"413":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"415":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"422":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"503":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Create or revision-conditionally update one resident schedule"}},"/v1/workflows":{"get":{"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowList"}}},"description":"Project-relative .nika.yaml names under the served registry (--workflows)"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Contained workflow names"}},"/v1/workflows/{name}":{"get":{"parameters":[{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowMetadata"}}},"description":"Contained name"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Error envelope"}},"summary":"Workflow metadata without source bytes"}}},"security":[{"bearerAuth":[]}],"servers":[{"url":"http://127.0.0.1"}]}
|