@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.
@@ -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.
@@ -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.
@@ -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"}]}