@supernovae-st/nika 0.120.2 → 0.120.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -346,8 +346,8 @@ node -p "require('@supernovae-st/nika/package.json').version"
346
346
  ./node_modules/.bin/nika --version
347
347
  ```
348
348
 
349
- This repository's source train is **0.120.2** (`package.json`), lockstep with
350
- public engine tag `v0.120.2` (`289a9adea`). A source train is not a published
349
+ This repository's source train is **0.120.3** (`package.json`), lockstep with
350
+ public engine tag `v0.120.3` (`578352a31`). A source train is not a published
351
351
  npm version until the release workflow publishes it.
352
352
 
353
353
  This package metadata subpath is exported for CommonJS, ESM build tools and CI
@@ -383,6 +383,52 @@ No destination means preview only. `nika.yaml` is the project control plane
383
383
  and asks for French; the hand-written file above is enough if it keeps
384
384
  `outputs.greeting` and `model: mock/echo`.
385
385
 
386
+ ## Compile a candidate without running it
387
+
388
+ `compile()` requires an engine that advertises the `compile` capability. The
389
+ released 0.120.3 engine supports native compilation and its Serve advertises
390
+ HTTP compilation (the route landed in engine commit `4334e58b`, first published
391
+ in v0.120.3); a 0.120.2 or older Serve predates that route and is refused
392
+ without fallback. This SDK method first publishes with 0.120.3. This foundation resolves
393
+ exact embedded skeleton names (including `hello`) and edits existing constants;
394
+ unsupported intent remains `incomplete`.
395
+
396
+ ```ts
397
+ const candidate = await nika.compile({
398
+ intent: 'classify-and-route',
399
+ answers: { 'const.request': 'An outage affects support customers.' },
400
+ }, { timeoutMs: 15_000 });
401
+
402
+ // The outcome is source and review data. Incomplete/refused are also outcomes.
403
+ console.log(candidate.status, candidate.questions, candidate.diagnostics);
404
+ if (candidate.ready && candidate.candidate !== null) {
405
+ const edited = await nika.compile({
406
+ workflow: candidate.candidate,
407
+ change: { set_constant: { name: 'request', value: 'One customer is affected.' } },
408
+ });
409
+ console.log(edited.candidate);
410
+ }
411
+ ```
412
+
413
+ Edits also accept a string such as `Set const.request to "One customer"`.
414
+ Answers and structured values are strict JSON values, preserving numbers,
415
+ booleans, null, strings, arrays and objects. The native adapter uses the engine's
416
+ CLI; the HTTP adapter sends authenticated `POST /v1/compile`. An unavailable
417
+ remote capability raises `NikaCompatibilityError` without local fallback.
418
+
419
+ `candidate` is `.nika` source, distinct from the path accepted by `run()`.
420
+ The caller reviews and materializes it before calling `run(path)`, which performs
421
+ normal admission. `requested_boundary` and the source-only `check_preview` grant
422
+ no execution authority. Compile creates no Run, job, approval or Proof, and the
423
+ SDK writes no persistent candidate file. `signal`/`timeoutMs` stop only the
424
+ compile request. The common outcome has no `exitCode` or `written` field.
425
+
426
+ Compile accepts standard `AbortSignal`s, including `AbortSignal.any` composites.
427
+ It rejects direct signal interface overrides and proxies before starting work.
428
+ Composite sources must retain their standard interfaces: Node may read their
429
+ public fields while inspecting or subscribing to a composite, so the SDK cannot
430
+ validate a modified hidden source graph without invoking those fields.
431
+
386
432
  ## Verify a local trace
387
433
 
388
434
  Local terminal results carry an engine-issued receipt when tracing is enabled.
package/dist/bin/nika.js CHANGED
@@ -150,29 +150,41 @@ function captureEngine(bin, args, options) {
150
150
  shell: false,
151
151
  stdio: ["ignore", "pipe", "pipe"]
152
152
  });
153
- let stdout = "";
154
- let stderr = "";
153
+ const stdout = [];
154
+ const stderr = [];
155
+ const bytes = { stdout: 0, stderr: 0 };
155
156
  let overflow = false;
156
157
  let spawnError;
158
+ let closed = false;
159
+ let killTimer;
160
+ const stop = () => {
161
+ child.kill("SIGTERM");
162
+ if (options.killGraceMs === void 0) return;
163
+ killTimer ??= setTimeout(() => {
164
+ if (!closed) child.kill("SIGKILL");
165
+ }, options.killGraceMs);
166
+ killTimer.unref();
167
+ };
157
168
  const append = (stream, chunk) => {
158
169
  if (overflow) return;
159
- if (stream === "stdout") stdout += chunk;
160
- else stderr += chunk;
161
- if (Buffer.byteLength(stdout) > options.bufferBytes || Buffer.byteLength(stderr) > options.bufferBytes) {
170
+ bytes[stream] += chunk.byteLength;
171
+ if (bytes[stream] > options.bufferBytes) {
162
172
  overflow = true;
163
- child.kill("SIGTERM");
173
+ stop();
174
+ return;
164
175
  }
176
+ (stream === "stdout" ? stdout : stderr).push(chunk);
165
177
  };
166
- child.stdout.setEncoding("utf8");
167
- child.stderr.setEncoding("utf8");
168
178
  child.stdout.on("data", (chunk) => append("stdout", chunk));
169
179
  child.stderr.on("data", (chunk) => append("stderr", chunk));
170
- const abort = () => child.kill("SIGTERM");
180
+ const abort = () => stop();
171
181
  options.signal?.addEventListener("abort", abort, { once: true });
172
182
  child.once("error", (cause) => {
173
183
  spawnError = cause;
174
184
  });
175
- child.once("close", (code) => {
185
+ child.once("close", (code, exitSignal) => {
186
+ closed = true;
187
+ if (killTimer) clearTimeout(killTimer);
176
188
  options.signal?.removeEventListener("abort", abort);
177
189
  if (options.signal?.aborted) {
178
190
  reject(new NikaTransportError(options.transport, `${options.label} aborted by caller`));
@@ -188,7 +200,19 @@ function captureEngine(bin, args, options) {
188
200
  `${options.label} exceeded ${options.bufferBytes} bytes`
189
201
  ));
190
202
  } else {
191
- resolve({ exitCode: code ?? 3, stdout, stderr });
203
+ let decoded;
204
+ try {
205
+ decoded = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.concat(stdout, bytes.stdout));
206
+ } catch {
207
+ reject(new NikaProtocolError(options.transport, `${options.label} stdout was not valid UTF-8`));
208
+ return;
209
+ }
210
+ resolve({
211
+ exitCode: code ?? 3,
212
+ stdout: decoded,
213
+ stderr: Buffer.concat(stderr, bytes.stderr).toString("utf8"),
214
+ exitSignal
215
+ });
192
216
  }
193
217
  });
194
218
  });
@@ -252,9 +276,9 @@ function incompatible(transport, message) {
252
276
 
253
277
  // src/lib/binary/verify.ts
254
278
  var IDENTITY_BUFFER_BYTES = 16 * 1024;
255
- async function verifyNikaEngine(engine) {
279
+ async function verifyNikaEngine(engine, options = {}) {
256
280
  const expectedVersion = engine.packageRoot ? await verifyManagedPayload(engine) : void 0;
257
- const identity = await probeIdentity(engine.bin);
281
+ const identity = await probeIdentity(engine.bin, options);
258
282
  if (expectedVersion !== void 0 && identity.engineVersion !== expectedVersion) {
259
283
  throw incompatible2(
260
284
  `Packaged engine version ${identity.engineVersion} does not match payload version ${expectedVersion}`
@@ -293,13 +317,15 @@ async function verifyManagedPayload(engine) {
293
317
  }
294
318
  return manifest.version;
295
319
  }
296
- async function probeIdentity(bin) {
320
+ async function probeIdentity(bin, options) {
297
321
  const notAnEngine = `is ${bin} a nika engine? (run "${bin} --sdk-identity" by hand)`;
298
322
  const captured = await captureEngine(bin, ["--sdk-identity"], {
323
+ ...options,
299
324
  bufferBytes: IDENTITY_BUFFER_BYTES,
300
325
  transport: "native-process",
301
326
  label: "Engine identity probe"
302
327
  }).catch((cause) => {
328
+ if (options.signal?.aborted) throw cause;
303
329
  throw incompatible2(
304
330
  `Engine identity probe of ${bin} failed`,
305
331
  cause