witnora 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
4
  import { createInterface } from "node:readline/promises";
5
+ import { fileURLToPath } from "node:url";
5
6
  import { validateEvidenceArtifacts } from "./artifact-validation.js";
6
7
  import { renderAgentCertBadge } from "./badge.js";
7
8
  import { buildEvidenceBundle } from "./bundle.js";
@@ -39,7 +40,8 @@ import { runOnboard } from "./onboard.js";
39
40
  import { inspectRepository } from "./onboard.js";
40
41
  import { renderReleaseEvaluation, runReleaseEvaluation } from "./release-evaluation.js";
41
42
  import { runPrivateCapabilityDiscovery } from "./private-discovery.js";
42
- import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, } from "./gateway.js";
43
+ import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, superviseManagedCustomerGateway, } from "./gateway.js";
44
+ import { installCurrentGatewayService, restartCurrentGatewayService, uninstallCurrentGatewayService } from "./gateway-service.js";
43
45
  import { verifyEvidencePacketV02 } from "./evidence-v02.js";
44
46
  process.on("uncaughtException", reportFatalError);
45
47
  process.on("unhandledRejection", reportFatalError);
@@ -224,6 +226,37 @@ else if (command === "gateway") {
224
226
  const result = await restartManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
225
227
  process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
226
228
  }
229
+ else if (action === "repair") {
230
+ const repository = readFlag("--repo") ?? process.cwd();
231
+ const dir = readFlag("--dir");
232
+ const service = await installCurrentGatewayService({ repository, gatewayDirectory: dir, cliEntry: fileURLToPath(import.meta.url) });
233
+ await restartCurrentGatewayService({ repository, gatewayDirectory: dir, cliEntry: fileURLToPath(import.meta.url) });
234
+ const result = await startManagedCustomerGateway({ repository, dir });
235
+ const doctor = await doctorCustomerGateway({ repository, dir });
236
+ if (!isGatewayDoctorReady(doctor))
237
+ throw new Error(`Connection repair did not restore a ready Gateway. ${doctor.nextAction}`);
238
+ process.stdout.write(`Connection repaired. ${service.plan.kind} ${service.plan.id} is installed and the Gateway is healthy.\n`);
239
+ process.stdout.write(renderManagedGatewayStatus(result));
240
+ }
241
+ else if (action === "service") {
242
+ const serviceAction = process.argv[4] ?? "install";
243
+ const repository = readFlag("--repo") ?? process.cwd();
244
+ const gatewayDirectory = readFlag("--dir");
245
+ const cliEntry = fileURLToPath(import.meta.url);
246
+ if (serviceAction === "install") {
247
+ const result = await installCurrentGatewayService({ repository, gatewayDirectory, cliEntry });
248
+ process.stdout.write(`Installed ${result.plan.kind} service ${result.plan.id}.\n`);
249
+ }
250
+ else if (serviceAction === "uninstall") {
251
+ const result = await uninstallCurrentGatewayService({ repository, gatewayDirectory, cliEntry });
252
+ process.stdout.write(`Removed Gateway service ${result.plan.id}.\n`);
253
+ }
254
+ else
255
+ throw new Error("Use witnora gateway service install|uninstall.");
256
+ }
257
+ else if (action === "supervise") {
258
+ await superviseManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir"), output: (message) => process.stdout.write(message) });
259
+ }
227
260
  else if (action === "stop") {
228
261
  const result = await stopManagedCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
229
262
  process.stdout.write(readBoolFlag("--json") ? `${JSON.stringify(result, null, 2)}\n` : renderManagedGatewayStatus(result));
@@ -249,7 +282,7 @@ else if (command === "gateway") {
249
282
  process.stdout.write(`Configured the Managed Workflow Harness for ${result.config.workflowIds?.length ?? "all active"} Workflow Contract(s).\nConfig: ${result.path}\nRestart the managed Gateway to activate it.\n`);
250
283
  }
251
284
  else {
252
- throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|stop|run|workflow-harness.");
285
+ throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|repair|service|stop|run|workflow-harness.");
253
286
  }
254
287
  }
255
288
  else if (command === "discover") {
@@ -63,6 +63,8 @@ Options:
63
63
  witnora gateway status
64
64
  witnora gateway logs [--lines 100]
65
65
  witnora gateway restart
66
+ witnora gateway repair
67
+ witnora gateway service install|uninstall
66
68
  witnora gateway stop
67
69
  witnora gateway run
68
70
  witnora gateway workflow-harness --workflow <id> --evaluator-origin http://127.0.0.1:<port>/ \\
@@ -71,8 +73,10 @@ Options:
71
73
  Initializes and manages a customer-owned, metadata-only collector beside the Agent.
72
74
  The browser approval issues a collector-scoped credential; no API key is copied into
73
75
  the repository or exposed to the Agent. The local queue and source signing key remain
74
- under customer control. Onboard uses background start by default. The run command is
75
- the foreground debugging path.
76
+ under customer control. The service command installs an OS-native auto-start and restart
77
+ boundary. Repair safely reinstalls that boundary, restarts the existing generated Gateway,
78
+ and reruns digest, credential, Probe, and health checks. The run command is the foreground
79
+ debugging path.
76
80
 
77
81
  The reference Gateway establishes RECORDED evidence only. ENFORCED requires target
78
82
  write credentials behind a controlled execution adapter. OUTCOME VERIFIED requires a
@@ -0,0 +1,104 @@
1
+ import { createHash } from "node:crypto";
2
+ import { execFile } from "node:child_process";
3
+ import { mkdir, rm, writeFile } from "node:fs/promises";
4
+ import { homedir, userInfo } from "node:os";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { promisify } from "node:util";
7
+ const execFileAsync = promisify(execFile);
8
+ export async function installGatewayService(input) {
9
+ const plan = createGatewayServicePlan(input);
10
+ await input.writeDefinition(plan.definitionPath, plan.definition);
11
+ if (plan.kind === "SYSTEMD_USER")
12
+ await input.run("systemctl", ["--user", "daemon-reload"]);
13
+ await input.run(plan.install.command, plan.install.args);
14
+ if (plan.kind !== "SYSTEMD_USER")
15
+ await input.run(plan.start.command, plan.start.args);
16
+ return { installed: true, plan };
17
+ }
18
+ export async function installCurrentGatewayService(options) {
19
+ const planInput = currentPlanInput(options);
20
+ return installGatewayService({ ...planInput,
21
+ writeDefinition: async (path, value) => { await mkdir(dirname(path), { recursive: true }); await writeFile(path, value, { encoding: "utf8", mode: 0o600 }); },
22
+ run: options.run ?? runCommand,
23
+ });
24
+ }
25
+ export async function uninstallCurrentGatewayService(options) {
26
+ const plan = createGatewayServicePlan(currentPlanInput(options));
27
+ const run = options.run ?? runCommand;
28
+ await run(plan.uninstall.command, plan.uninstall.args);
29
+ if (plan.kind === "SYSTEMD_USER")
30
+ await run("systemctl", ["--user", "daemon-reload"]);
31
+ await rm(plan.definitionPath, { force: true });
32
+ return { uninstalled: true, plan };
33
+ }
34
+ export async function restartCurrentGatewayService(options) {
35
+ const plan = createGatewayServicePlan(currentPlanInput(options));
36
+ const run = options.run ?? runCommand;
37
+ await run(plan.stop.command, plan.stop.args).catch(() => undefined);
38
+ await run(plan.start.command, plan.start.args);
39
+ return plan;
40
+ }
41
+ export function createGatewayServicePlan(input) {
42
+ const suffix = createHash("sha256").update(`${input.repository}\n${input.gatewayDirectory}`).digest("hex").slice(0, 12);
43
+ const id = `witnora-gateway-${suffix}`;
44
+ const superviseArgs = [input.cliEntry, "gateway", "supervise", "--repo", input.repository, "--dir", input.gatewayDirectory];
45
+ if (input.platform === "win32")
46
+ return windowsPlan(input, id, superviseArgs);
47
+ if (input.platform === "darwin")
48
+ return launchdPlan(input, id, superviseArgs);
49
+ if (input.platform === "linux")
50
+ return systemdPlan(input, id, superviseArgs);
51
+ throw new Error(`Gateway service installation is not supported on ${input.platform}.`);
52
+ }
53
+ function windowsPlan(input, id, args) {
54
+ const definitionPath = `${input.serviceHome}\\${id}.xml`;
55
+ const argumentsValue = args.map(windowsArgument).join(" ");
56
+ const definition = `<?xml version="1.0" encoding="UTF-8"?>
57
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
58
+ <Triggers><LogonTrigger><Enabled>true</Enabled><UserId>${xml(input.userId)}</UserId></LogonTrigger></Triggers>
59
+ <Principals><Principal id="Author"><UserId>${xml(input.userId)}</UserId><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
60
+ <Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><StartWhenAvailable>true</StartWhenAvailable><ExecutionTimeLimit>PT0S</ExecutionTimeLimit><RestartOnFailure><Interval>PT1M</Interval><Count>999</Count></RestartOnFailure></Settings>
61
+ <Actions Context="Author"><Exec><Command>${xml(input.nodeExecutable)}</Command><Arguments>${xml(argumentsValue)}</Arguments><WorkingDirectory>${xml(input.repository)}</WorkingDirectory></Exec></Actions>
62
+ </Task>`;
63
+ return { id, kind: "WINDOWS_TASK", definitionPath, definition,
64
+ install: { command: "schtasks.exe", args: ["/Create", "/TN", id, "/XML", definitionPath, "/F"] },
65
+ start: { command: "schtasks.exe", args: ["/Run", "/TN", id] },
66
+ stop: { command: "schtasks.exe", args: ["/End", "/TN", id] },
67
+ uninstall: { command: "schtasks.exe", args: ["/Delete", "/TN", id, "/F"] } };
68
+ }
69
+ function currentPlanInput(options) {
70
+ const platform = options.platform ?? process.platform;
71
+ if (platform !== "win32" && platform !== "linux" && platform !== "darwin")
72
+ throw new Error(`Gateway service installation is not supported on ${platform}.`);
73
+ const repository = resolve(options.repository ?? process.cwd());
74
+ const env = options.env ?? process.env;
75
+ const serviceHome = platform === "win32" ? join(env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "Witnora", "services")
76
+ : platform === "darwin" ? join(homedir(), "Library", "LaunchAgents") : join(homedir(), ".config", "systemd", "user");
77
+ return { platform, repository, gatewayDirectory: resolve(repository, options.gatewayDirectory ?? ".witnora/gateway"), cliEntry: resolve(options.cliEntry), nodeExecutable: options.nodeExecutable ?? process.execPath, serviceHome, userId: platform === "win32" ? `${env.USERDOMAIN ? `${env.USERDOMAIN}\\` : ""}${env.USERNAME ?? userInfo().username}` : userInfo().username };
78
+ }
79
+ async function runCommand(command, args) { await execFileAsync(command, args, { windowsHide: true }); }
80
+ function systemdPlan(input, id, args) {
81
+ const unit = `${id}.service`;
82
+ const definitionPath = `${input.serviceHome}/${unit}`;
83
+ const definition = `[Unit]\nDescription=Witnora customer-owned Gateway\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${systemd(input.repository)}\nExecStart=${[input.nodeExecutable, ...args].map(systemd).join(" ")}\nRestart=always\nRestartSec=5\n\n[Install]\nWantedBy=default.target\n`;
84
+ return { id, kind: "SYSTEMD_USER", definitionPath, definition,
85
+ install: { command: "systemctl", args: ["--user", "enable", "--now", unit] },
86
+ start: { command: "systemctl", args: ["--user", "start", unit] },
87
+ stop: { command: "systemctl", args: ["--user", "stop", unit] },
88
+ uninstall: { command: "systemctl", args: ["--user", "disable", "--now", unit] } };
89
+ }
90
+ function launchdPlan(input, id, args) {
91
+ const label = `com.witnora.${id}`;
92
+ const definitionPath = `${input.serviceHome}/${label}.plist`;
93
+ const programArguments = [input.nodeExecutable, ...args].map((value) => `<string>${xml(value)}</string>`).join("");
94
+ const definition = `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict><key>Label</key><string>${label}</string><key>ProgramArguments</key><array>${programArguments}</array><key>WorkingDirectory</key><string>${xml(input.repository)}</string><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>5</integer></dict></plist>`;
95
+ const domain = `gui/${process.getuid?.() ?? input.userId}`;
96
+ return { id, kind: "LAUNCHD_AGENT", definitionPath, definition,
97
+ install: { command: "launchctl", args: ["bootstrap", domain, definitionPath] },
98
+ start: { command: "launchctl", args: ["kickstart", `${domain}/${label}`] },
99
+ stop: { command: "launchctl", args: ["kill", "SIGTERM", `${domain}/${label}`] },
100
+ uninstall: { command: "launchctl", args: ["bootout", domain, definitionPath] } };
101
+ }
102
+ function windowsArgument(value) { return `"${value.replaceAll('"', '\\"')}"`; }
103
+ function systemd(value) { return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`; }
104
+ function xml(value) { return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;"); }
package/dist/gateway.js CHANGED
@@ -514,6 +514,10 @@ export async function runCustomerGateway(options) {
514
514
  host: process.env.WITNORA_GATEWAY_HOST?.trim() || config.host,
515
515
  port: gatewayPort(process.env.WITNORA_GATEWAY_PORT, config.port),
516
516
  environment: "customer-owned",
517
+ onRepairRequested: async ({ requestId }) => {
518
+ process.stderr.write(`Hosted connection repair ${requestId} requested a verified Gateway restart.\n`);
519
+ setTimeout(() => process.exit(75), 25).unref();
520
+ },
517
521
  ...(actionWorker && config.runtimeWorker ? { actionWorker: {
518
522
  track: (input) => isConfiguredRuntimeProposal(input.proposal, config.runtimeWorker) ? actionWorker.track(input) : Promise.resolve(undefined),
519
523
  status: async () => {
@@ -1056,6 +1060,25 @@ export async function restartManagedCustomerGateway(options = {}) {
1056
1060
  await stopManagedCustomerGateway(options);
1057
1061
  return startManagedCustomerGateway(options);
1058
1062
  }
1063
+ export async function superviseManagedCustomerGateway(options = {}) {
1064
+ const inspect = options.inspect ?? (() => statusManagedCustomerGateway({ repository: options.repository, dir: options.dir }));
1065
+ const start = options.start ?? (() => startManagedCustomerGateway({ repository: options.repository, dir: options.dir }));
1066
+ const sleep = options.sleep ?? wait;
1067
+ const maxCycles = options.maxCycles ?? Number.POSITIVE_INFINITY;
1068
+ let recoveries = 0;
1069
+ for (let cycle = 0; cycle < maxCycles; cycle += 1) {
1070
+ const status = await inspect();
1071
+ if (status.state === "CONFLICT")
1072
+ throw new Error(status.detail);
1073
+ if (status.state === "STOPPED" || status.state === "STALE") {
1074
+ await start();
1075
+ recoveries += 1;
1076
+ options.output?.(`Recovered customer-owned Gateway (${recoveries}).\n`);
1077
+ }
1078
+ if (cycle + 1 < maxCycles)
1079
+ await sleep(options.intervalMs ?? 5_000);
1080
+ }
1081
+ }
1059
1082
  export async function readManagedCustomerGatewayLogs(options = {}) {
1060
1083
  const repository = resolve(options.repository ?? process.cwd());
1061
1084
  const directory = resolve(repository, options.dir ?? ".witnora/gateway");
@@ -50,6 +50,10 @@ export interface CustomerOwnedCollectorGatewayOptions {
50
50
  status(): Promise<AssuranceHarnessHeartbeat>;
51
51
  };
52
52
  runtimeBinding?: RuntimeReadinessBinding;
53
+ onRepairRequested?: (request: {
54
+ requestId: string;
55
+ action: "RESTART_AND_VERIFY";
56
+ }) => void | Promise<void>;
53
57
  }
54
58
  export interface CustomerOwnedCollectorGateway {
55
59
  baseUrl: string;
@@ -1 +1 @@
1
- {"version":3,"file":"collector-gateway.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/collector-gateway.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collector-gateway.d.ts","sourceRoot":"","sources":["../../../../agentcert-sdk/src/collector-gateway.ts"],"names":[],"mappings":"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"}
@@ -176,7 +176,7 @@ export async function startCustomerOwnedCollectorGateway(options) {
176
176
  try {
177
177
  await ensureSourceKeyRegistered();
178
178
  const workerStatus = options.actionWorker ? await options.actionWorker.status() : undefined;
179
- await options.client.heartbeat({
179
+ const response = await options.client.heartbeat({
180
180
  collectorId: activeCollector.id,
181
181
  signer,
182
182
  pendingRecordCount: current.pendingRecordCount,
@@ -184,6 +184,13 @@ export async function startCustomerOwnedCollectorGateway(options) {
184
184
  runtime: runtimeBinding && workerStatus ? runtimeHeartbeat(runtimeBinding, workerStatus, options.actionWorker?.runtimeBinding?.fixtureReady) : undefined,
185
185
  assuranceHarness: options.assuranceHarness ? await options.assuranceHarness.status() : undefined,
186
186
  });
187
+ const repair = response.repair;
188
+ if (repair && typeof repair === "object" && !Array.isArray(repair)) {
189
+ const requestId = repair.requestId;
190
+ const action = repair.action;
191
+ if (typeof requestId === "string" && action === "RESTART_AND_VERIFY")
192
+ await options.onRepairRequested?.({ requestId, action });
193
+ }
187
194
  lastRemoteSuccessAt = new Date().toISOString();
188
195
  lastRemoteError = undefined;
189
196
  }
package/dist/onboard.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { access, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { DEFAULT_WITNORA_SERVER, saveConnection } from "./credentials.js";
5
6
  import { authorizeProjectConnection } from "./device-authorization.js";
6
7
  import { verifyControlPlaneConnection } from "./control-plane.js";
@@ -11,6 +12,7 @@ import { doctorCustomerGateway, activateManagedWorkflowHarness, initializeCustom
11
12
  import { discoverRuntimeReferences, planRuntimeSandboxModules } from "./runtime-sandbox-kit.js";
12
13
  import { automaticRuntimeReferencesUsable, bootstrapLocalRuntime, verifyAutomaticRuntimeProbe } from "./runtime-bootstrap.js";
13
14
  import { activateRealPathIntegrations } from "./real-path-activation.js";
15
+ import { installCurrentGatewayService } from "./gateway-service.js";
14
16
  export async function runOnboard(options) {
15
17
  const requestFetch = options.fetch ?? fetch;
16
18
  const output = options.output ?? ((message) => process.stdout.write(message));
@@ -91,6 +93,7 @@ export async function runOnboard(options) {
91
93
  limitation: "No approved customer Harness module is bound to this Agent repository.",
92
94
  };
93
95
  let assuranceHarnessChanged = false;
96
+ let continuousService;
94
97
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
95
98
  try {
96
99
  generatedFiles.push(...await generateRepositoryConfig(repositoryPath, repository.template, repository.name));
@@ -176,6 +179,10 @@ export async function runOnboard(options) {
176
179
  }
177
180
  }
178
181
  else {
182
+ const stopped = await gatewayLifecycle.stop({ repository: repositoryPath, configHome: options.configHome, fetch: requestFetch });
183
+ if (stopped.state === "STOPPED" || stopped.state === "STALE") {
184
+ output(`\nStopped the managed Gateway for project ${binding.projectId} before rebinding this repository.\n`);
185
+ }
179
186
  await assertGatewayStopped(requestFetch, binding.host, binding.port, binding.projectId, token.projectId);
180
187
  gatewayMigration = await archiveGateway(gatewayState.directory, repositoryPath, binding.projectId);
181
188
  output(`\nExisting Gateway belongs to project ${binding.projectId}; archived it at ${gatewayMigration.archiveDirectory}.\n`);
@@ -209,6 +216,10 @@ export async function runOnboard(options) {
209
216
  if (stopped.state !== "STOPPED" && stopped.state !== "STALE")
210
217
  throw new Error("The prior managed Gateway process did not stop before Assurance Harness activation.");
211
218
  }
219
+ if (!options.gatewayLifecycle) {
220
+ const installed = await installCurrentGatewayService({ repository: repositoryPath, cliEntry: fileURLToPath(new URL("./cli.js", import.meta.url)) });
221
+ continuousService = { state: "INSTALLED", kind: installed.plan.kind, id: installed.plan.id };
222
+ }
212
223
  managedGateway = await gatewayLifecycle.start({
213
224
  repository: repositoryPath,
214
225
  configHome: options.configHome,
@@ -233,6 +244,8 @@ export async function runOnboard(options) {
233
244
  output(`Private discovery: ${discovery.capabilityCount} capability group(s); ${discovery.unknownCapabilityCount} pending confirmation.\n`);
234
245
  output("Installed: customer-owned Gateway, default-deny policy, independent-probe contract, review contract, and PR/release/nightly CI.\n");
235
246
  output(`Gateway: ${managedGateway.state} at ${managedGateway.baseUrl}${managedGateway.pid ? ` (process ${managedGateway.pid})` : ""}.\n`);
247
+ if (continuousService)
248
+ output(`Continuous operation: ${continuousService.kind} ${continuousService.id} will start at login and recover the Gateway after process failure.\n`);
236
249
  output(runtimeReadiness.state === "LOCAL_SANDBOX_READY"
237
250
  ? "Runtime: LOCAL_SANDBOX_READY. This proves only the generated localhost sandbox loop is ready; it does not establish coverage, CURRENT, or a verified customer outcome.\n"
238
251
  : `Runtime: RECORDED_ONLY. ${runtimeReadiness.limitations[0]}\n`);
@@ -243,7 +256,7 @@ export async function runOnboard(options) {
243
256
  return { projectId: token.projectId, connectionName: token.connectionName, credentialsPath, template: repository.template,
244
257
  repositoryKind: repository.kind, generatedFiles, receiptPath, discovery, setupPlanId: setupPlan.id,
245
258
  gatewayDirectory: join(repositoryPath, ".witnora", "gateway"), gatewayArchiveDirectory: gatewayMigration?.archiveDirectory,
246
- gateway: managedGateway, runtimeReadiness, assuranceHarnessReadiness };
259
+ gateway: managedGateway, runtimeReadiness, assuranceHarnessReadiness, continuousService };
247
260
  }
248
261
  catch (error) {
249
262
  const diagnosis = error instanceof Error ? error.message : String(error);
@@ -49,8 +49,11 @@ export async function bootstrapLocalRuntime(input) {
49
49
  body: JSON.stringify({ ...contract, contractFingerprintSha256 }),
50
50
  });
51
51
  const body = await response.json().catch(() => ({}));
52
- if (!response.ok)
53
- throw new Error(`Hosted Runtime bootstrap did not complete (HTTP ${response.status}).`);
52
+ if (!response.ok) {
53
+ const detail = typeof body.error === "string" ? body.error : typeof body.message === "string" ? body.message : undefined;
54
+ const code = typeof body.code === "string" ? body.code : undefined;
55
+ throw new Error(`Hosted Runtime bootstrap did not complete (HTTP ${response.status})${code ? ` [${code}]` : ""}${detail ? `: ${detail.slice(0, 300)}` : "."}`);
56
+ }
54
57
  const issued = validateResponse(body, contractFingerprintSha256, attempt.deliveryPublicKeyPem);
55
58
  const probeCredentialPath = join(directory, "outcome-probe-api-key.txt");
56
59
  const probeCredentialMetadataPath = join(directory, "outcome-probe-api-key.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",