witnora 0.18.10 → 0.18.12

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/gateway.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createHash, createPrivateKey, createPublicKey, randomBytes } from "node:crypto";
1
+ import { createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { closeSync, openSync } from "node:fs";
4
4
  import { access, chmod, mkdir, readFile, realpath, rename, rm, writeFile } from "node:fs/promises";
@@ -8,6 +8,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
8
8
  import { loadConnection } from "./credentials.js";
9
9
  import { authorizeProjectConnection } from "./device-authorization.js";
10
10
  import { verifyHostedProbeCredential } from "./runtime-bootstrap.js";
11
+ import { activateRealPathIntegrations } from "./real-path-activation.js";
11
12
  import { inspectIsolatedOutcomeProbe, observeInIsolatedOutcomeProbe } from "./probe-process.js";
12
13
  import { findAvailableRuntimeSandboxOrigin, LOCAL_SANDBOX_FIXTURE_CONTRACT_SHA256, startRuntimeSandboxFixture, } from "./runtime-sandbox-fixture.js";
13
14
  import { generateRuntimeSandboxKit, LOCAL_SANDBOX_ADAPTER_ID, LOCAL_SANDBOX_ADAPTER_VERSION, LOCAL_SANDBOX_PROBE_ID, } from "./runtime-sandbox-kit.js";
@@ -60,6 +61,7 @@ export async function initializeCustomerGateway(options) {
60
61
  port: gatewayPort(process.env.WITNORA_GATEWAY_PORT, 8787),
61
62
  storageDirectory: "data",
62
63
  privacyMode: "metadata_only",
64
+ ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}),
63
65
  coverage: runtimeKit
64
66
  ? { recorded: "configured", enforced: "configured", outcomeVerified: "configured" }
65
67
  : { recorded: "configured", enforced: "not_configured", outcomeVerified: "not_configured" },
@@ -135,14 +137,20 @@ export async function upgradeCustomerGatewayRuntime(options) {
135
137
  if (current.projectId !== options.authorization.projectId || current.server !== options.authorization.server)
136
138
  throw new Error("Existing Gateway project/server binding does not match this authorized Runtime upgrade.");
137
139
  const generatedClient = clientRaw === gatewayClient(current)
138
- || (!current.runtimeWorker && clientRaw === recordedGatewayClient(current));
140
+ || (!current.runtimeWorker && (clientRaw === recordedGatewayClient(current)
141
+ || clientRaw === recordedGatewayClient({ ...current, agentIdentity: undefined })));
139
142
  if (!generatedClient || readmeRaw !== gatewayReadme(current))
140
143
  throw new Error("Existing Gateway files differ from the Witnora-generated version; refusing to overwrite them.");
141
144
  if (current.runtimeWorker && current.runtimeWorker.adapterId !== LOCAL_SANDBOX_ADAPTER_ID) {
142
145
  throw new Error("Existing Gateway already has a different Runtime worker; preserve it and reconfigure in Advanced mode.");
143
146
  }
144
147
  const runtimeKit = await prepareRuntimeSandboxKit(options.runtimeReferences, options.authorization, options.configHome, options.fetch ?? fetch, directory);
145
- const next = { ...current, coverage: { recorded: "configured", enforced: "configured", outcomeVerified: "configured" }, runtimeWorker: runtimeKit.config };
148
+ const next = {
149
+ ...current,
150
+ ...(options.agentIdentity ? { agentIdentity: options.agentIdentity } : {}),
151
+ coverage: { recorded: "configured", enforced: "configured", outcomeVerified: "configured" },
152
+ runtimeWorker: runtimeKit.config,
153
+ };
146
154
  next.runtimeWorker.configDigestSha256 = gatewayConfigDigest(next);
147
155
  const replacingGeneratedRuntime = current.runtimeWorker?.adapterId === LOCAL_SANDBOX_ADAPTER_ID;
148
156
  if (replacingGeneratedRuntime) {
@@ -154,7 +162,8 @@ export async function upgradeCustomerGatewayRuntime(options) {
154
162
  || createHash("sha256").update(probeBytes).digest("hex") !== current.runtimeWorker.probeModuleSha256) {
155
163
  throw new Error("Existing generated Runtime modules were modified; refusing to overwrite them.");
156
164
  }
157
- if (sameRuntimeGeneration(current.runtimeWorker, next.runtimeWorker)) {
165
+ if (sameRuntimeGeneration(current.runtimeWorker, next.runtimeWorker)
166
+ && JSON.stringify(current.agentIdentity) === JSON.stringify(next.agentIdentity)) {
158
167
  return { config: current, generatedFiles: [], changed: false, rollback: async () => undefined };
159
168
  }
160
169
  }
@@ -509,10 +518,6 @@ export async function runCustomerGateway(options) {
509
518
  throw new Error("The saved Gateway credential does not match gateway.json. Run gateway init again.");
510
519
  }
511
520
  const dataDirectory = resolve(directory, config.storageDirectory);
512
- const workflowHarnessConfigPath = join(directory, "workflow-harness.json");
513
- const workflowHarnessConfig = await exists(workflowHarnessConfigPath)
514
- ? parseManagedWorkflowHarnessConfig(await readFile(workflowHarnessConfigPath, "utf8"))
515
- : undefined;
516
521
  const keyRingPath = join(dataDirectory, "source-keys.json");
517
522
  const keyRing = await (await exists(keyRingPath)
518
523
  ? CustomerSourceKeyRing.open(keyRingPath)
@@ -534,16 +539,10 @@ export async function runCustomerGateway(options) {
534
539
  FileActionCheckpointStore: (await durableWorker).FileActionCheckpointStore,
535
540
  })
536
541
  : undefined;
537
- const workflowHarness = workflowHarnessConfig
538
- ? await createConfiguredWorkflowHarness({
539
- directory,
540
- projectId: config.projectId,
541
- server: config.server,
542
- apiKey: connection.apiKey,
543
- config: workflowHarnessConfig,
544
- managed: await configManagedWorkflowHarnessImport(),
545
- })
546
- : undefined;
542
+ const assuranceController = await createContinuousAssuranceController({
543
+ repository: resolve(directory, "..", ".."), directory, projectId: config.projectId, server: config.server,
544
+ apiKey: connection.apiKey, config, sourceSigner: () => keyRing.activeSigner(),
545
+ });
547
546
  actionWorker?.start();
548
547
  const gateway = await startCustomerOwnedCollectorGateway({
549
548
  client: new RemoteCollectorClient({ baseUrl: connection.server, projectId: connection.projectId, apiKey: connection.apiKey }),
@@ -592,19 +591,17 @@ export async function runCustomerGateway(options) {
592
591
  fixtureReady: false,
593
592
  } } : {}),
594
593
  } } : {}),
595
- ...(workflowHarnessConfig?.schemaVersion === MANAGED_WORKFLOW_HARNESS_SCHEMA && workflowHarness
596
- ? { assuranceHarness: { status: () => workflowHarness.status() } }
597
- : {}),
594
+ assuranceHarness: { status: () => assuranceController.status() },
598
595
  });
599
- workflowHarness?.start();
596
+ assuranceController.start();
600
597
  process.stdout.write(`Witnora customer-owned Gateway listening on ${gateway.baseUrl}\n`);
601
598
  process.stdout.write(config.runtimeWorker
602
599
  ? "Runtime worker: READY. Approved exact configured actions execute automatically, then use the separate read-only probe and Hosted signed receipt.\n"
603
600
  : "Evidence ceiling: RECORDED. No exact target adapter and separate outcome probe are configured, so runtime writes remain fail-closed.\n");
604
- if (workflowHarness)
605
- process.stdout.write("Managed Workflow Harness: CONFIGURED. Only authority-ready Replay and no-write Shadow evaluations will be sent to the pinned customer evaluator.\n");
601
+ if (await assuranceController.status())
602
+ process.stdout.write("Managed Workflow Harness: ACTIVE. Approved task changes are detected and revalidated automatically.\n");
606
603
  for (const signal of ["SIGINT", "SIGTERM"]) {
607
- process.once(signal, () => void Promise.allSettled([gateway.close(), ...(workflowHarness ? [workflowHarness.close()] : []), ...(sandboxFixture ? [sandboxFixture.close()] : [])]).finally(() => process.exit(0)));
604
+ process.once(signal, () => void Promise.allSettled([gateway.close(), assuranceController.close(), ...(sandboxFixture ? [sandboxFixture.close()] : [])]).finally(() => process.exit(0)));
608
605
  }
609
606
  }
610
607
  catch (error) {
@@ -727,6 +724,210 @@ export async function createConfiguredWorkflowHarness(input) {
727
724
  tick,
728
725
  };
729
726
  }
727
+ function configSignedChangeManifestImport() {
728
+ return import(new URL("./vendor/onegent-runtime/signed-change-manifest.js", import.meta.url).href);
729
+ }
730
+ /**
731
+ * Reconciles the existing approved-task, Real Path, Manifest, and Autopilot
732
+ * contracts from inside the already-running Gateway. No new service or durable
733
+ * orchestration state is introduced; local config remains the recoverable cache.
734
+ */
735
+ export async function createContinuousAssuranceController(input) {
736
+ const request = input.fetch ?? fetch;
737
+ const managed = input.managed ?? await configManagedWorkflowHarnessImport();
738
+ let harness;
739
+ let harnessFingerprint;
740
+ let timer;
741
+ let reconciling = false;
742
+ let closing = false;
743
+ let lastReportedError;
744
+ const replaceHarness = async (config) => {
745
+ const fingerprint = createHash("sha256").update(JSON.stringify(config)).digest("hex");
746
+ if (harness && fingerprint === harnessFingerprint)
747
+ return;
748
+ const next = await createConfiguredWorkflowHarness({
749
+ directory: input.directory,
750
+ projectId: input.projectId,
751
+ server: input.server,
752
+ apiKey: input.apiKey,
753
+ config,
754
+ managed,
755
+ evaluatorKit: input.evaluatorKit,
756
+ fetch: request,
757
+ });
758
+ next.start();
759
+ const previous = harness;
760
+ harness = next;
761
+ harnessFingerprint = fingerprint;
762
+ await previous?.close();
763
+ };
764
+ const workflowHarnessPath = join(input.directory, "workflow-harness.json");
765
+ if (await exists(workflowHarnessPath)) {
766
+ try {
767
+ await replaceHarness(parseManagedWorkflowHarnessConfig(await readFile(workflowHarnessPath, "utf8")));
768
+ }
769
+ catch (error) {
770
+ lastReportedError = message(error);
771
+ process.stderr.write(`Existing Assurance Harness will be repaired in the background: ${lastReportedError}\n`);
772
+ }
773
+ }
774
+ const reconcile = async () => {
775
+ if (closing || reconciling)
776
+ return;
777
+ reconciling = true;
778
+ try {
779
+ const activation = await activateRealPathIntegrations({
780
+ repository: input.repository,
781
+ server: input.server,
782
+ projectId: input.projectId,
783
+ apiKey: input.apiKey,
784
+ fetch: request,
785
+ });
786
+ if (activation.state === "READY_TO_START") {
787
+ const configured = await activateManagedWorkflowHarness({
788
+ repository: input.repository,
789
+ modulePath: activation.modulePath,
790
+ realPathActivations: activation.activations,
791
+ previousGeneratedModuleSha256: activation.previousGeneratedModuleSha256,
792
+ });
793
+ await replaceHarness(configured.config);
794
+ }
795
+ await publishCurrentChangeManifests({ ...input, fetch: request }, harness ? await harness.status() : undefined);
796
+ lastReportedError = undefined;
797
+ }
798
+ catch (error) {
799
+ const current = message(error).slice(0, 500);
800
+ if (current !== lastReportedError)
801
+ process.stderr.write(`Continuous Assurance reconciliation is waiting safely: ${current}\n`);
802
+ lastReportedError = current;
803
+ }
804
+ finally {
805
+ reconciling = false;
806
+ }
807
+ };
808
+ return {
809
+ start() {
810
+ if (timer || closing)
811
+ return;
812
+ void reconcile();
813
+ timer = setInterval(() => void reconcile(), input.intervalMs ?? 15_000);
814
+ timer.unref?.();
815
+ },
816
+ reconcile,
817
+ status: () => harness?.status() ?? Promise.resolve(undefined),
818
+ async close() {
819
+ closing = true;
820
+ if (timer)
821
+ clearInterval(timer);
822
+ timer = undefined;
823
+ await harness?.close();
824
+ harness = undefined;
825
+ },
826
+ };
827
+ }
828
+ async function publishCurrentChangeManifests(input, heartbeat) {
829
+ const base = input.server.replace(/\/$/, "");
830
+ const [activationResponse, manifestResponse] = await Promise.all([
831
+ input.fetch(`${base}/v1/projects/${encodeURIComponent(input.projectId)}/nora/activation-plan`, { headers: { authorization: `Bearer ${input.apiKey}` }, redirect: "error", signal: AbortSignal.timeout(10_000) }),
832
+ input.fetch(`${base}/v1/projects/${encodeURIComponent(input.projectId)}/change-manifests`, { headers: { authorization: `Bearer ${input.apiKey}` }, redirect: "error", signal: AbortSignal.timeout(10_000) }),
833
+ ]);
834
+ if (!activationResponse.ok)
835
+ throw new Error(`Assurance activation plan returned HTTP ${activationResponse.status}.`);
836
+ if (!manifestResponse.ok)
837
+ throw new Error(`Signed Change Manifest collection returned HTTP ${manifestResponse.status}.`);
838
+ const activation = await boundedResponseJson(activationResponse, 512 * 1024);
839
+ const collection = await boundedResponseJson(manifestResponse, 512 * 1024);
840
+ const targets = Array.isArray(activation.targets) ? activation.targets.filter(validActivationTarget) : [];
841
+ const manifests = Array.isArray(collection.manifests) ? collection.manifests.filter(validChangeManifestRecord) : [];
842
+ if (!targets.length)
843
+ return;
844
+ const packageDigest = await optionalFileDigest(join(input.repository, "package.json"));
845
+ const discoveryDigest = await optionalFileDigest(join(input.repository, "witnora.discovery.json"));
846
+ const policyRevision = createHash("sha256").update(JSON.stringify({ privacyMode: input.config.privacyMode, coverage: input.config.coverage })).digest("hex");
847
+ const explicitModelDigest = process.env.WITNORA_MODEL_DIGEST?.trim();
848
+ const explicitPromptWorkflowDigest = process.env.WITNORA_PROMPT_WORKFLOW_DIGEST?.trim();
849
+ const runtime = input.config.runtimeWorker;
850
+ const components = {
851
+ ...(explicitModelDigest && /^[a-f0-9]{64}$/.test(explicitModelDigest) ? { modelDigest: explicitModelDigest } : {}),
852
+ ...(validSha256(explicitPromptWorkflowDigest) ? { promptWorkflowDigest: explicitPromptWorkflowDigest } : packageDigest ? { promptWorkflowDigest: packageDigest } : {}),
853
+ ...(discoveryDigest ? { toolManifestDigest: discoveryDigest } : {}),
854
+ policyRevision,
855
+ ...(heartbeat ? { evaluatorIdentity: { id: "witnora-managed-evaluator", version: "1", digestSha256: heartbeat.evaluatorContractSha256 } } : {}),
856
+ ...(runtime ? { adapterIdentity: { id: runtime.adapterId, version: runtime.adapterVersion, digestSha256: runtime.adapterModuleSha256 } } : {}),
857
+ };
858
+ const signed = input.signedChangeManifest ?? await configSignedChangeManifestImport();
859
+ const unique = new Map();
860
+ const exactBindings = heartbeat?.realPathActivations ?? [];
861
+ for (const target of targets) {
862
+ for (const environment of target.environments) {
863
+ if (!exactBindings.some((binding) => binding.agentId === target.agentId && binding.environment === environment))
864
+ continue;
865
+ unique.set(`${target.agentId}:${environment}`, { agentId: target.agentId, agentVersion: target.currentAgentVersion, environment });
866
+ }
867
+ }
868
+ for (const target of unique.values()) {
869
+ const previous = manifests.find((manifest) => manifest.agentId === target.agentId && manifest.environment === target.environment);
870
+ if (previous && previous.agentVersion === target.agentVersion && sameManifestComponents(previous, components))
871
+ continue;
872
+ const observedAt = new Date().toISOString();
873
+ const payload = {
874
+ schemaVersion: "witnora.signed_change_manifest.v0.1",
875
+ manifestId: `gateway-${randomUUID()}`,
876
+ projectId: input.projectId,
877
+ agentId: target.agentId,
878
+ agentVersion: target.agentVersion,
879
+ environment: target.environment,
880
+ observedAt,
881
+ ...components,
882
+ ...(previous?.manifestDigestSha256 ? { previousManifestDigest: previous.manifestDigestSha256 } : {}),
883
+ };
884
+ const envelope = signed.createSignedChangeManifestEnvelope(payload, input.sourceSigner());
885
+ await signed.uploadSignedChangeManifest({ baseUrl: base, projectId: input.projectId, apiKey: input.apiKey, envelope, fetch: input.fetch });
886
+ }
887
+ }
888
+ function validActivationTarget(value) {
889
+ if (!value || typeof value !== "object" || Array.isArray(value))
890
+ return false;
891
+ const target = value;
892
+ return typeof target.agentId === "string" && typeof target.currentAgentVersion === "string"
893
+ && Array.isArray(target.environments) && target.environments.every((environment) => environment === "sandbox" || environment === "staging" || environment === "production");
894
+ }
895
+ function validChangeManifestRecord(value) {
896
+ if (!value || typeof value !== "object" || Array.isArray(value))
897
+ return false;
898
+ const manifest = value;
899
+ return typeof manifest.agentId === "string" && typeof manifest.agentVersion === "string"
900
+ && (manifest.environment === "sandbox" || manifest.environment === "staging" || manifest.environment === "production")
901
+ && typeof manifest.manifestDigestSha256 === "string";
902
+ }
903
+ function sameManifestComponents(previous, next) {
904
+ return previous.modelDigest === next.modelDigest && previous.promptWorkflowDigest === next.promptWorkflowDigest
905
+ && previous.toolManifestDigest === next.toolManifestDigest && previous.policyRevision === next.policyRevision
906
+ && JSON.stringify(previous.evaluatorIdentity) === JSON.stringify(next.evaluatorIdentity)
907
+ && JSON.stringify(previous.adapterIdentity) === JSON.stringify(next.adapterIdentity);
908
+ }
909
+ async function optionalFileDigest(path) {
910
+ try {
911
+ return createHash("sha256").update(await readFile(path)).digest("hex");
912
+ }
913
+ catch (error) {
914
+ if (error.code === "ENOENT")
915
+ return undefined;
916
+ throw error;
917
+ }
918
+ }
919
+ function validSha256(value) {
920
+ return Boolean(value && /^[a-f0-9]{64}$/.test(value));
921
+ }
922
+ async function boundedResponseJson(response, maxBytes) {
923
+ const text = await response.text();
924
+ if (Buffer.byteLength(text) > maxBytes)
925
+ throw new Error("Hosted Assurance response exceeded its size limit.");
926
+ const value = JSON.parse(text);
927
+ if (!value || typeof value !== "object" || Array.isArray(value))
928
+ throw new Error("Hosted Assurance response is invalid.");
929
+ return value;
930
+ }
730
931
  async function startManagedLocalEvaluator(directory, config, evaluatorKit) {
731
932
  const repository = resolve(directory, "..", "..");
732
933
  const modulePath = resolve(repository, config.evaluatorModulePath);
@@ -1165,6 +1366,8 @@ function parseConfig(raw) {
1165
1366
  }
1166
1367
  if (value.privacyMode !== "metadata_only")
1167
1368
  throw new Error("Gateway privacyMode must remain metadata_only.");
1369
+ if (value.agentIdentity)
1370
+ validateGatewayAgentIdentity(value.agentIdentity);
1168
1371
  if (!value.host || !Number.isSafeInteger(value.port) || Number(value.port) < 1 || Number(value.port) > 65_535)
1169
1372
  throw new Error("Gateway host and port are invalid.");
1170
1373
  if (value.runtimeWorker)
@@ -1175,6 +1378,13 @@ function parseConfig(raw) {
1175
1378
  }
1176
1379
  return config;
1177
1380
  }
1381
+ function validateGatewayAgentIdentity(value) {
1382
+ for (const [key, item] of Object.entries(value)) {
1383
+ if (typeof item !== "string" || item.trim().length === 0 || item.length > 200) {
1384
+ throw new Error(`Gateway agentIdentity.${key} must be a non-empty string no longer than 200 characters.`);
1385
+ }
1386
+ }
1387
+ }
1178
1388
  export function parseManagedWorkflowHarnessConfig(raw) {
1179
1389
  const value = JSON.parse(raw);
1180
1390
  const workflowIds = value.workflowIds;
@@ -1459,7 +1669,11 @@ async function ed25519PublicKeyPem(handle) {
1459
1669
  return createPublicKey(key).export({ type: "spki", format: "pem" }).toString();
1460
1670
  }
1461
1671
  function recordedGatewayClient(config) {
1462
- return `import { readFile } from "node:fs/promises";\n\nconst baseUrl = "http://${config.host}:${config.port}";\nlet gatewayToken;\n\nasync function token() {\n if (gatewayToken) return gatewayToken;\n const secrets = JSON.parse(await readFile(new URL("./secrets.json", import.meta.url), "utf8"));\n if (typeof secrets.gatewayToken !== "string" || secrets.gatewayToken.length < 32) {\n throw new Error("Witnora local Gateway token is missing or invalid.");\n }\n gatewayToken = secrets.gatewayToken;\n return gatewayToken;\n}\n\nasync function post(runId, operation, body) {\n if (!/^[A-Za-z0-9._:-]+$/.test(runId)) throw new Error("Witnora runId contains unsupported characters.");\n const response = await fetch(\`\${baseUrl}/v1/runs/\${encodeURIComponent(runId)}/\${operation}\`, {\n method: "POST",\n headers: { authorization: \`Bearer \${await token()}\`, "content-type": "application/json" },\n body: JSON.stringify(body),\n });\n const result = await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(result.error ?? \`Witnora Gateway returned HTTP \${response.status}.\`);\n return result;\n}\n\nexport const witnoraGateway = {\n start(runId, metadata = {}) {\n return post(runId, "start", { payload: metadata, idempotencyKey: "run-start" });\n },\n event(runId, type, metadata = {}, idempotencyKey = \`\${type}-\${crypto.randomUUID()}\`) {\n return post(runId, "events", { type, payload: metadata, idempotencyKey });\n },\n complete(runId, metadata = {}) {\n return post(runId, "complete", {\n payload: metadata,\n evidenceStrength: {\n schemaVersion: "agentcert.evidence_strength.v0.1",\n level: "recorded",\n claims: [],\n limitations: ["No write-credential mediation or independent outcome probe is configured."],\n },\n idempotencyKey: "run-complete",\n });\n },\n};\n`;
1672
+ const defaultAgent = config.agentIdentity ? JSON.stringify(config.agentIdentity) : undefined;
1673
+ const startPayload = defaultAgent
1674
+ ? `const payload = { ...metadata, agent: metadata.agent ?? ${defaultAgent} };\n return post(runId, "start", { payload, idempotencyKey: "run-start" });`
1675
+ : `return post(runId, "start", { payload: metadata, idempotencyKey: "run-start" });`;
1676
+ return `import { readFile } from "node:fs/promises";\n\nconst baseUrl = "http://${config.host}:${config.port}";\nlet gatewayToken;\n\nasync function token() {\n if (gatewayToken) return gatewayToken;\n const secrets = JSON.parse(await readFile(new URL("./secrets.json", import.meta.url), "utf8"));\n if (typeof secrets.gatewayToken !== "string" || secrets.gatewayToken.length < 32) {\n throw new Error("Witnora local Gateway token is missing or invalid.");\n }\n gatewayToken = secrets.gatewayToken;\n return gatewayToken;\n}\n\nasync function post(runId, operation, body) {\n if (!/^[A-Za-z0-9._:-]+$/.test(runId)) throw new Error("Witnora runId contains unsupported characters.");\n const response = await fetch(\`\${baseUrl}/v1/runs/\${encodeURIComponent(runId)}/\${operation}\`, {\n method: "POST",\n headers: { authorization: \`Bearer \${await token()}\`, "content-type": "application/json" },\n body: JSON.stringify(body),\n });\n const result = await response.json().catch(() => ({}));\n if (!response.ok) throw new Error(result.error ?? \`Witnora Gateway returned HTTP \${response.status}.\`);\n return result;\n}\n\nexport const witnoraGateway = {\n start(runId, metadata = {}) {\n ${startPayload}\n },\n event(runId, type, metadata = {}, idempotencyKey = \`\${type}-\${crypto.randomUUID()}\`) {\n return post(runId, "events", { type, payload: metadata, idempotencyKey });\n },\n complete(runId, metadata = {}) {\n return post(runId, "complete", {\n payload: metadata,\n evidenceStrength: {\n schemaVersion: "agentcert.evidence_strength.v0.1",\n level: "recorded",\n claims: [],\n limitations: ["No write-credential mediation or independent outcome probe is configured."],\n },\n idempotencyKey: "run-complete",\n });\n },\n};\n`;
1463
1677
  }
1464
1678
  async function writeExclusive(path, content, force, mode) {
1465
1679
  if (!force && await exists(path))
@@ -48,7 +48,7 @@ export interface CustomerOwnedCollectorGatewayOptions {
48
48
  };
49
49
  };
50
50
  assuranceHarness?: {
51
- status(): Promise<AssuranceHarnessHeartbeat>;
51
+ status(): Promise<AssuranceHarnessHeartbeat | undefined>;
52
52
  };
53
53
  runtimeBinding?: RuntimeReadinessBinding;
54
54
  onRepairRequested?: (request: {
@@ -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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"}
@@ -76,6 +76,7 @@ export async function startCustomerOwnedCollectorGateway(options) {
76
76
  return { delivered, reconciled, pending };
77
77
  };
78
78
  const status = async () => {
79
+ const assuranceHarness = options.assuranceHarness ? await options.assuranceHarness.status() : undefined;
79
80
  let pendingRecordCount = 0;
80
81
  let lastAckSequence;
81
82
  for (const journal of journals.values()) {
@@ -94,7 +95,7 @@ export async function startCustomerOwnedCollectorGateway(options) {
94
95
  lastRemoteSuccessAt,
95
96
  lastRemoteError,
96
97
  ...(options.actionWorker ? { actionWorker: await options.actionWorker.status() } : {}),
97
- ...(options.assuranceHarness ? { assuranceHarness: await options.assuranceHarness.status() } : {}),
98
+ ...(assuranceHarness ? { assuranceHarness } : {}),
98
99
  ...(monitoringControlFor() ? { monitoring: monitoringControlFor() } : {}),
99
100
  };
100
101
  };
package/dist/onboard.js CHANGED
@@ -19,6 +19,12 @@ export async function runOnboard(options) {
19
19
  const server = normalizeServer(options.server ?? DEFAULT_WITNORA_SERVER);
20
20
  const repositoryPath = resolve(options.repository ?? process.cwd());
21
21
  const repository = await inspectRepository(repositoryPath, options.template);
22
+ const agentIdentity = {
23
+ externalId: repository.name,
24
+ name: humanizeAgentName(repository.name),
25
+ version: repository.version,
26
+ framework: frameworkForTemplate(repository.template),
27
+ };
22
28
  const connectionName = options.name ?? repository.slug;
23
29
  const token = await authorizeProjectConnection({
24
30
  projectId: options.projectId,
@@ -95,6 +101,24 @@ export async function runOnboard(options) {
95
101
  };
96
102
  let assuranceHarnessChanged = false;
97
103
  let continuousService;
104
+ const prepareRuntime = async (operation) => {
105
+ const delays = [250, 500, 1_000, 2_000, 4_000, 8_000];
106
+ let lastError;
107
+ for (let attempt = 0; attempt <= delays.length; attempt += 1) {
108
+ try {
109
+ return await operation();
110
+ }
111
+ catch (error) {
112
+ if (!(error instanceof RuntimeSetupNotReadyError))
113
+ throw error;
114
+ lastError = error;
115
+ if (attempt === delays.length)
116
+ break;
117
+ await (options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds))))(delays[attempt]);
118
+ }
119
+ }
120
+ throw lastError;
121
+ };
98
122
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, { status: "installing", attemptId });
99
123
  try {
100
124
  generatedFiles.push(...await generateRepositoryConfig(repositoryPath, repository.template, repository.name));
@@ -137,11 +161,12 @@ export async function runOnboard(options) {
137
161
  if (gatewayState.status === "absent") {
138
162
  let gateway;
139
163
  try {
140
- gateway = await initializeCustomerGateway({
164
+ gateway = await prepareRuntime(() => initializeCustomerGateway({
141
165
  projectId: token.projectId, server, repository: repositoryPath, authorization: token,
142
166
  runtimeReferences: localRuntime.references,
167
+ agentIdentity,
143
168
  fetch: requestFetch, configHome: options.configHome, output,
144
- });
169
+ }));
145
170
  }
146
171
  catch (error) {
147
172
  if (!(error instanceof RuntimeSetupNotReadyError) || !localRuntime.references)
@@ -157,7 +182,7 @@ export async function runOnboard(options) {
157
182
  await saveConnection(binding.connectionName, { server, projectId: token.projectId, apiKey: token.apiKey }, { configHome: options.configHome });
158
183
  if (localRuntime.references) {
159
184
  try {
160
- runtimeUpgrade = await upgradeCustomerGatewayRuntime({ repository: repositoryPath, authorization: token, runtimeReferences: localRuntime.references, configHome: options.configHome, fetch: requestFetch });
185
+ runtimeUpgrade = await prepareRuntime(() => upgradeCustomerGatewayRuntime({ repository: repositoryPath, authorization: token, runtimeReferences: localRuntime.references, agentIdentity, configHome: options.configHome, fetch: requestFetch }));
161
186
  if (runtimeUpgrade.changed) {
162
187
  const stopped = await gatewayLifecycle.stop({ repository: repositoryPath, configHome: options.configHome, fetch: requestFetch });
163
188
  if (stopped.state !== "STOPPED" || stopped.healthy)
@@ -187,11 +212,12 @@ export async function runOnboard(options) {
187
212
  await assertGatewayStopped(requestFetch, binding.host, binding.port, binding.projectId, token.projectId);
188
213
  gatewayMigration = await archiveGateway(gatewayState.directory, repositoryPath, binding.projectId);
189
214
  output(`\nExisting Gateway belongs to project ${binding.projectId}; archived it at ${gatewayMigration.archiveDirectory}.\n`);
190
- const gateway = await initializeCustomerGateway({
215
+ const gateway = await prepareRuntime(() => initializeCustomerGateway({
191
216
  projectId: token.projectId, server, repository: repositoryPath, authorization: token,
192
217
  runtimeReferences: localRuntime.references,
218
+ agentIdentity,
193
219
  fetch: requestFetch, configHome: options.configHome, output,
194
- });
220
+ }));
195
221
  generatedFiles.push(...gateway.generatedFiles);
196
222
  }
197
223
  }
@@ -252,7 +278,7 @@ export async function runOnboard(options) {
252
278
  : { state: "RECORDED_ONLY", checkedAt: new Date().toISOString(), limitations: [runtimeLimitation ?? "The local sandbox Runtime worker has not established exact adapter, probe, source-key, identity, mandate, and readiness bindings."] };
253
279
  await reportInstall(requestFetch, server, token.projectId, token.apiKey, setupPlan.id, {
254
280
  status: "verified", attemptId, generatedFiles: relativeGeneratedFiles(repositoryPath, generatedFiles), runtimeReadiness,
255
- connectedAgent: { externalId: repository.name, name: humanizeAgentName(repository.name), version: repository.version, framework: frameworkForTemplate(repository.template) },
281
+ connectedAgent: agentIdentity,
256
282
  ...(runtimeBinding ? { runtimeBinding } : {}),
257
283
  });
258
284
  output(`\nWitnora Setup Autopilot completed for ${repository.name}.\n`);
@@ -11,11 +11,25 @@ export async function activateRealPathIntegrations(options) {
11
11
  if (!response.ok)
12
12
  throw new Error(`Could not load approved real-path integrations (${response.status}).`);
13
13
  const body = await boundedJson(response);
14
- const approved = Array.isArray(body.integrations) ? body.integrations.map(parsePlan).filter((plan) => plan.status === "READY_TO_ACTIVATE") : [];
14
+ const approved = Array.isArray(body.integrations) ? body.integrations.map(parsePlan).filter((plan) => plan.status === "READY_TO_ACTIVATE" || plan.status === "HARNESS_ACTIVE") : [];
15
15
  const plans = approved.filter((plan) => ["STRIPE_REFUND", "SHOPIFY_DISPUTE", "ZENDESK_TICKET", "SALESFORCE_RECORD", "HUBSPOT_CRM_RECORD", "POSTGRES_RECORD"].includes(plan.generated.providerPackId) && plan.environment === "sandbox" && plan.customerSummary.evaluationMode === "SHADOW");
16
16
  if (!plans.length)
17
17
  return { state: "NOT_CONFIGURED", created: false, activations: [], generatedFiles: [], rollback: async () => undefined };
18
18
  const environment = options.env ?? process.env;
19
+ const modulePath = join(repository, "witnora.assurance-harness.mjs");
20
+ const manifestPath = join(repository, ".witnora", "gateway", "real-path-activations.json");
21
+ const source = generatedProviderHarness(plans);
22
+ const moduleDigestSha256 = sha(source);
23
+ const current = await readFile(modulePath, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
24
+ const priorSource = await readFile(manifestPath, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
25
+ const prior = priorSource === undefined ? undefined : JSON.parse(priorSource);
26
+ const cachedActivations = prior?.projectId === options.projectId && prior.moduleDigestSha256 === moduleDigestSha256 && current === source
27
+ && Array.isArray(prior.activations) && sameActivationPlans(plans, prior.activations) ? structuredClone(prior.activations) : undefined;
28
+ if (cachedActivations) {
29
+ for (const plan of plans)
30
+ await persistProviderCredential(repository, plan, environment);
31
+ return { state: "READY_TO_START", created: false, modulePath: "witnora.assurance-harness.mjs", activations: cachedActivations, generatedFiles: [], previousGeneratedModuleSha256: moduleDigestSha256, rollback: async () => undefined };
32
+ }
19
33
  const activations = [];
20
34
  const scenarios = [];
21
35
  for (const plan of plans) {
@@ -25,15 +39,8 @@ export async function activateRealPathIntegrations(options) {
25
39
  activations.push({ integrationId: plan.id, integrationDigestSha256: plan.digestSha256, taskContractId: plan.taskContractId, taskContractDigestSha256: plan.taskContractDigestSha256, agentId: plan.subject.agentId, agentVersion: plan.subject.agentVersion, environment: plan.environment, providerPackId: plan.generated.providerPackId, providerContractDigestSha256: plan.generated.providerContractDigestSha256, actionPathIds: plan.generated.actionPathBindings.map((item) => item.actionPathId), acceptance });
26
40
  scenarios.push({ plan, value: [preflight.scenario] });
27
41
  }
28
- const modulePath = join(repository, "witnora.assurance-harness.mjs");
29
- const manifestPath = join(repository, ".witnora", "gateway", "real-path-activations.json");
30
- const source = generatedProviderHarness(plans);
31
- const moduleDigestSha256 = sha(source);
32
42
  let created = false;
33
43
  const generatedFiles = [];
34
- const current = await readFile(modulePath, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
35
- const priorSource = await readFile(manifestPath, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
36
- const prior = priorSource === undefined ? undefined : JSON.parse(priorSource);
37
44
  const previousGeneratedModuleSha256 = current !== undefined && sha(current) === prior?.moduleDigestSha256 ? prior.moduleDigestSha256 : undefined;
38
45
  if (current === undefined) {
39
46
  await writeFile(modulePath, source, { encoding: "utf8", mode: 0o600, flag: "wx" });
@@ -253,16 +260,30 @@ async function persistProviderCredential(repository, plan, environment) {
253
260
  return;
254
261
  const name = providerCredentialEnvironmentName(plan.generated.providerPackId);
255
262
  const value = environment[name];
256
- if (!value)
257
- throw new Error(`${plan.generated.providerPackId} activation requires its read-only credential in the customer environment.`);
258
263
  const directory = join(repository, ".witnora", "provider-credentials");
259
264
  const target = join(directory, `${plan.id}.secret`);
260
- const temporary = `${target}.${randomUUID()}.tmp`;
265
+ const current = await readFile(target, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
266
+ if (!value) {
267
+ if (current !== undefined)
268
+ return;
269
+ throw new Error(`${plan.generated.providerPackId} activation requires its read-only credential in the customer environment.`);
270
+ }
271
+ if (current === value)
272
+ return;
261
273
  await mkdir(directory, { recursive: true });
262
274
  await writeFile(join(directory, ".gitignore"), "*\n!.gitignore\n", { encoding: "utf8", mode: 0o600, flag: "wx" }).catch((error) => { if (error.code !== "EEXIST")
263
275
  throw error; });
264
- await writeFile(temporary, value, { encoding: "utf8", mode: 0o600, flag: "wx" });
265
- await rename(temporary, target);
276
+ await writeFile(target, value, { encoding: "utf8", mode: 0o600 });
277
+ }
278
+ function sameActivationPlans(plans, activations) {
279
+ if (plans.length !== activations.length)
280
+ return false;
281
+ return plans.every((plan) => activations.some((activation) => activation.integrationId === plan.id && activation.integrationDigestSha256 === plan.digestSha256
282
+ && activation.taskContractId === plan.taskContractId && activation.taskContractDigestSha256 === plan.taskContractDigestSha256
283
+ && activation.agentId === plan.subject.agentId && activation.agentVersion === plan.subject.agentVersion && activation.environment === plan.environment
284
+ && activation.providerPackId === plan.generated.providerPackId && activation.providerContractDigestSha256 === plan.generated.providerContractDigestSha256
285
+ && canonical([...activation.actionPathIds].sort()) === canonical(plan.generated.actionPathBindings.map((item) => item.actionPathId).sort())
286
+ && activation.acceptance?.kind === "READ_ONLY_PROVIDER_PREFLIGHT" && activation.acceptance.productionWrites === 0 && digest(activation.acceptance.observationDigestSha256)));
266
287
  }
267
288
  function providerCredentialEnvironmentName(packId) {
268
289
  if (packId === "STRIPE_REFUND")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.18.10",
3
+ "version": "0.18.12",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",