witnora 0.13.7 → 0.13.9

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
@@ -1,5 +1,14 @@
1
1
  # Witnora CLI
2
2
 
3
+ ## Customer Evaluator Kit
4
+
5
+ `witnora/evaluator-kit` exports `createBusinessTaskEvaluatorServer()` for the customer-side
6
+ Replay and Shadow evaluator consumed by the managed Workflow Harness. It listens only on a
7
+ literal loopback address, requires a pinned bearer credential and contract digest, validates
8
+ the exact Business Task binding, and returns only the structured evaluation report. Customer
9
+ scenario inputs and candidate callbacks stay in the customer process. Replay can execute only
10
+ through the supplied synthetic sandbox harness; Shadow exposes no write interface.
11
+
3
12
  Unified release assurance, evidence, corpus, monitor, and lab CLI for Witnora.
4
13
 
5
14
  Witnora checks what an agent may do, whether it passed pre-release evidence,
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import { runOnboard } from "./onboard.js";
39
39
  import { inspectRepository } from "./onboard.js";
40
40
  import { renderReleaseEvaluation, runReleaseEvaluation } from "./release-evaluation.js";
41
41
  import { runPrivateCapabilityDiscovery } from "./private-discovery.js";
42
- import { doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, } from "./gateway.js";
42
+ import { configureManagedWorkflowHarness, doctorCustomerGateway, initializeCustomerGateway, isGatewayDoctorReady, readManagedCustomerGatewayLogs, renderGatewayDoctor, renderManagedGatewayStatus, restartManagedCustomerGateway, runCustomerGateway, startManagedCustomerGateway, statusManagedCustomerGateway, stopManagedCustomerGateway, } from "./gateway.js";
43
43
  import { verifyEvidencePacketV02 } from "./evidence-v02.js";
44
44
  process.on("uncaughtException", reportFatalError);
45
45
  process.on("unhandledRejection", reportFatalError);
@@ -231,8 +231,25 @@ else if (command === "gateway") {
231
231
  else if (action === "run") {
232
232
  await runCustomerGateway({ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir") });
233
233
  }
234
+ else if (action === "workflow-harness") {
235
+ const workflowIds = process.argv.flatMap((argument, index) => argument === "--workflow" && process.argv[index + 1] ? [process.argv[index + 1]] : []);
236
+ const evaluatorOrigin = readFlag("--evaluator-origin");
237
+ const evaluatorCredentialHandle = readFlag("--evaluator-credential-handle");
238
+ const evaluatorContractSha256 = readFlag("--evaluator-contract-sha256");
239
+ if (!workflowIds.length || !evaluatorOrigin || !evaluatorCredentialHandle || !evaluatorContractSha256) {
240
+ throw new Error("Use witnora gateway workflow-harness --workflow <id> --evaluator-origin http://127.0.0.1:<port>/ --evaluator-credential-handle file://... --evaluator-contract-sha256 <sha256>.");
241
+ }
242
+ const result = await configureManagedWorkflowHarness({
243
+ repository: readFlag("--repo") ?? process.cwd(), dir: readFlag("--dir"), workflowIds,
244
+ evaluatorOrigin, evaluatorCredentialHandle, evaluatorContractSha256,
245
+ pollIntervalMs: readFlag("--poll-ms") ? Number(readFlag("--poll-ms")) : undefined,
246
+ maxConcurrency: readFlag("--max-concurrency") ? Number(readFlag("--max-concurrency")) : undefined,
247
+ force: readBoolFlag("--force"),
248
+ });
249
+ process.stdout.write(`Configured the Managed Workflow Harness for ${result.config.workflowIds.length} exact Workflow Contract(s).\nConfig: ${result.path}\nRestart the managed Gateway to activate it.\n`);
250
+ }
234
251
  else {
235
- throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|stop|run.");
252
+ throw new Error("Use witnora gateway init|doctor|start|status|logs|restart|stop|run|workflow-harness.");
236
253
  }
237
254
  }
238
255
  else if (command === "discover") {
@@ -65,6 +65,8 @@ Options:
65
65
  witnora gateway restart
66
66
  witnora gateway stop
67
67
  witnora gateway run
68
+ witnora gateway workflow-harness --workflow <id> --evaluator-origin http://127.0.0.1:<port>/ \\
69
+ --evaluator-credential-handle file://... --evaluator-contract-sha256 <sha256>
68
70
 
69
71
  Initializes and manages a customer-owned, metadata-only collector beside the Agent.
70
72
  The browser approval issues a collector-scoped credential; no API key is copied into
@@ -86,6 +88,12 @@ Options:
86
88
  --force Replace an existing reviewed local setup
87
89
  --json JSON status output
88
90
  --lines <count> Number of recent log lines (default: 100)
91
+ --workflow <id> Exact active Workflow Contract (repeatable)
92
+ --evaluator-origin Literal-loopback customer evaluator origin
93
+ --evaluator-credential-handle Opaque file:// bearer credential reference
94
+ --evaluator-contract-sha256 Pinned evaluator contract digest
95
+ --poll-ms <milliseconds> Workflow plan interval (1000-60000)
96
+ --max-concurrency <count> Bounded ready evaluations (1-16)
89
97
  `;
90
98
  if (command === "design-partner")
91
99
  return `Usage:
package/dist/gateway.js CHANGED
@@ -13,6 +13,7 @@ import { generateRuntimeSandboxKit, LOCAL_SANDBOX_ADAPTER_ID, LOCAL_SANDBOX_ADAP
13
13
  const CONFIG_SCHEMA = "witnora.customer_gateway_setup.v0.1";
14
14
  const SECRETS_SCHEMA = "witnora.customer_gateway_local_secrets.v0.1";
15
15
  const RUNTIME_SCHEMA = "witnora.managed_gateway_runtime.v0.1";
16
+ const WORKFLOW_HARNESS_SCHEMA = "witnora.managed_workflow_harness.v0.1";
16
17
  export async function initializeCustomerGateway(options) {
17
18
  const repository = resolve(options.repository ?? process.cwd());
18
19
  const outDir = resolve(repository, options.outDir ?? ".witnora/gateway");
@@ -468,6 +469,10 @@ export async function runCustomerGateway(options) {
468
469
  throw new Error("The saved Gateway credential does not match gateway.json. Run gateway init again.");
469
470
  }
470
471
  const dataDirectory = resolve(directory, config.storageDirectory);
472
+ const workflowHarnessConfigPath = join(directory, "workflow-harness.json");
473
+ const workflowHarnessConfig = await exists(workflowHarnessConfigPath)
474
+ ? parseManagedWorkflowHarnessConfig(await readFile(workflowHarnessConfigPath, "utf8"))
475
+ : undefined;
471
476
  const keyRingPath = join(dataDirectory, "source-keys.json");
472
477
  const keyRing = await (await exists(keyRingPath)
473
478
  ? CustomerSourceKeyRing.open(keyRingPath)
@@ -489,6 +494,16 @@ export async function runCustomerGateway(options) {
489
494
  FileActionCheckpointStore: (await durableWorker).FileActionCheckpointStore,
490
495
  })
491
496
  : undefined;
497
+ const workflowHarness = workflowHarnessConfig
498
+ ? await createConfiguredWorkflowHarness({
499
+ directory,
500
+ projectId: config.projectId,
501
+ server: config.server,
502
+ apiKey: connection.apiKey,
503
+ config: workflowHarnessConfig,
504
+ managed: await configManagedWorkflowHarnessImport(),
505
+ })
506
+ : undefined;
492
507
  actionWorker?.start();
493
508
  const gateway = await startCustomerOwnedCollectorGateway({
494
509
  client: new RemoteCollectorClient({ baseUrl: connection.server, projectId: connection.projectId, apiKey: connection.apiKey }),
@@ -533,12 +548,15 @@ export async function runCustomerGateway(options) {
533
548
  } } : {}),
534
549
  } } : {}),
535
550
  });
551
+ workflowHarness?.start();
536
552
  process.stdout.write(`Witnora customer-owned Gateway listening on ${gateway.baseUrl}\n`);
537
553
  process.stdout.write(config.runtimeWorker
538
554
  ? "Runtime worker: READY. Approved exact configured actions execute automatically, then use the separate read-only probe and Hosted signed receipt.\n"
539
555
  : "Evidence ceiling: RECORDED. No exact target adapter and separate outcome probe are configured, so runtime writes remain fail-closed.\n");
556
+ if (workflowHarness)
557
+ 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");
540
558
  for (const signal of ["SIGINT", "SIGTERM"]) {
541
- process.once(signal, () => void Promise.allSettled([gateway.close(), ...(sandboxFixture ? [sandboxFixture.close()] : [])]).finally(() => process.exit(0)));
559
+ process.once(signal, () => void Promise.allSettled([gateway.close(), ...(workflowHarness ? [workflowHarness.close()] : []), ...(sandboxFixture ? [sandboxFixture.close()] : [])]).finally(() => process.exit(0)));
542
560
  }
543
561
  }
544
562
  catch (error) {
@@ -546,6 +564,76 @@ export async function runCustomerGateway(options) {
546
564
  throw error;
547
565
  }
548
566
  }
567
+ function configManagedWorkflowHarnessImport() {
568
+ return import(new URL("./vendor/onegent-runtime/managed-workflow-harness.js", import.meta.url).href);
569
+ }
570
+ export async function createConfiguredWorkflowHarness(input) {
571
+ const requestFetch = input.fetch ?? fetch;
572
+ const credential = await readSecretProviderHandle(input.config.evaluatorCredentialHandle);
573
+ const health = await requestFetch(`${input.config.evaluatorOrigin}/healthz`, {
574
+ headers: { authorization: `Bearer ${credential}` },
575
+ redirect: "error",
576
+ signal: AbortSignal.timeout(2_000),
577
+ });
578
+ const healthBody = await health.json().catch(() => ({}));
579
+ if (!health.ok
580
+ || healthBody.schemaVersion !== "witnora.business_task_evaluator.v0.1"
581
+ || healthBody.contractSha256 !== input.config.evaluatorContractSha256
582
+ || healthBody.ready !== true) {
583
+ throw new Error("Managed Workflow evaluator health did not match its pinned local contract.");
584
+ }
585
+ const hosted = input.managed.createManagedWorkflowHostedClient({
586
+ baseUrl: input.server,
587
+ projectId: input.projectId,
588
+ apiKey: input.apiKey,
589
+ workflowIds: input.config.workflowIds,
590
+ fetch: requestFetch,
591
+ });
592
+ const worker = new input.managed.ManagedBusinessWorkflowHarness({
593
+ client: hosted,
594
+ checkpoints: new input.managed.FileWorkflowEvaluationCheckpointStore(join(input.directory, "data", "workflow-evaluations")),
595
+ maxConcurrency: input.config.maxConcurrency,
596
+ evaluate: async ({ task, evaluation }) => {
597
+ const response = await requestFetch(`${input.config.evaluatorOrigin}/v1/evaluate`, {
598
+ method: "POST",
599
+ headers: { authorization: `Bearer ${credential}`, "content-type": "application/json" },
600
+ body: JSON.stringify({ schemaVersion: "witnora.business_task_evaluation_request.v0.1", task, evaluation }),
601
+ redirect: "error",
602
+ signal: AbortSignal.timeout(120_000),
603
+ });
604
+ const body = await response.json().catch(() => ({}));
605
+ if (!response.ok || !body.report || typeof body.report !== "object" || Array.isArray(body.report)) {
606
+ throw new Error(`Managed Workflow evaluator failed (${response.status}).`);
607
+ }
608
+ return body.report;
609
+ },
610
+ });
611
+ let timer;
612
+ let closing = false;
613
+ const tick = async () => {
614
+ const result = await worker.tick();
615
+ if ((result.evaluationsFailed ?? 0) > 0) {
616
+ process.stderr.write(`Managed Workflow Harness stopped ${result.evaluationsFailed} evaluation(s) fail-closed. ${result.limitations?.[0] ?? "Inspect the customer evaluator and retry."}\n`);
617
+ }
618
+ return result;
619
+ };
620
+ return {
621
+ start() {
622
+ if (timer || closing)
623
+ return;
624
+ void tick().catch((error) => process.stderr.write(`Managed Workflow Harness tick failed: ${error instanceof Error ? error.message : "unknown error"}\n`));
625
+ timer = setInterval(() => void tick().catch((error) => process.stderr.write(`Managed Workflow Harness tick failed: ${error instanceof Error ? error.message : "unknown error"}\n`)), input.config.pollIntervalMs ?? 5_000);
626
+ timer.unref?.();
627
+ },
628
+ async close() {
629
+ closing = true;
630
+ if (timer)
631
+ clearInterval(timer);
632
+ timer = undefined;
633
+ },
634
+ tick,
635
+ };
636
+ }
549
637
  async function runtimeSandboxFixtureReady(config) {
550
638
  try {
551
639
  if (config.adapterId !== LOCAL_SANDBOX_ADAPTER_ID || !config.sandboxOrigin || !config.probeTargetCredentialHandle || !config.fixtureContractSha256)
@@ -936,6 +1024,63 @@ function parseConfig(raw) {
936
1024
  }
937
1025
  return config;
938
1026
  }
1027
+ export function parseManagedWorkflowHarnessConfig(raw) {
1028
+ const value = JSON.parse(raw);
1029
+ if (value.schemaVersion !== WORKFLOW_HARNESS_SCHEMA
1030
+ || value.enabled !== true
1031
+ || !literalLoopbackOrigin(value.evaluatorOrigin)
1032
+ || !credentialHandle(value.evaluatorCredentialHandle ?? "")
1033
+ || !validDigest(value.evaluatorContractSha256 ?? "")
1034
+ || !Array.isArray(value.workflowIds)
1035
+ || !value.workflowIds.length
1036
+ || value.workflowIds.length > 100
1037
+ || new Set(value.workflowIds).size !== value.workflowIds.length
1038
+ || value.workflowIds.some((id) => typeof id !== "string" || !/^[A-Za-z0-9._:-]{1,200}$/.test(id))) {
1039
+ throw new Error("workflow-harness.json requires an enabled, digest-pinned literal-loopback evaluator and a local credential handle.");
1040
+ }
1041
+ if (value.pollIntervalMs !== undefined && (!Number.isInteger(value.pollIntervalMs) || value.pollIntervalMs < 1_000 || value.pollIntervalMs > 60_000)) {
1042
+ throw new Error("workflow-harness.json pollIntervalMs must be between 1000 and 60000.");
1043
+ }
1044
+ if (value.maxConcurrency !== undefined && (!Number.isInteger(value.maxConcurrency) || value.maxConcurrency < 1 || value.maxConcurrency > 16)) {
1045
+ throw new Error("workflow-harness.json maxConcurrency must be between 1 and 16.");
1046
+ }
1047
+ return value;
1048
+ }
1049
+ export async function configureManagedWorkflowHarness(options) {
1050
+ const directory = resolve(options.repository ?? process.cwd(), options.dir ?? ".witnora/gateway");
1051
+ if (!await exists(join(directory, "gateway.json")))
1052
+ throw new Error("Initialize the customer-owned Gateway before configuring its Workflow Harness.");
1053
+ const config = parseManagedWorkflowHarnessConfig(JSON.stringify({
1054
+ schemaVersion: WORKFLOW_HARNESS_SCHEMA,
1055
+ enabled: true,
1056
+ evaluatorOrigin: options.evaluatorOrigin,
1057
+ evaluatorCredentialHandle: options.evaluatorCredentialHandle,
1058
+ evaluatorContractSha256: options.evaluatorContractSha256,
1059
+ workflowIds: options.workflowIds,
1060
+ ...(options.pollIntervalMs === undefined ? {} : { pollIntervalMs: options.pollIntervalMs }),
1061
+ ...(options.maxConcurrency === undefined ? {} : { maxConcurrency: options.maxConcurrency }),
1062
+ }));
1063
+ const path = join(directory, "workflow-harness.json");
1064
+ await writeExclusive(path, `${JSON.stringify(config, null, 2)}\n`, options.force ?? false, 0o600);
1065
+ return { path, config };
1066
+ }
1067
+ function literalLoopbackOrigin(value) {
1068
+ if (typeof value !== "string")
1069
+ return false;
1070
+ try {
1071
+ const url = new URL(value);
1072
+ return url.protocol === "http:"
1073
+ && (url.hostname === "127.0.0.1" || url.hostname === "[::1]")
1074
+ && url.username === ""
1075
+ && url.password === ""
1076
+ && url.pathname === "/"
1077
+ && url.search === ""
1078
+ && url.hash === "";
1079
+ }
1080
+ catch {
1081
+ return false;
1082
+ }
1083
+ }
939
1084
  function validateRuntimeWorkerConfig(value) {
940
1085
  if (value.enabled !== true || !value.adapterModulePath || !/^[a-f0-9]{64}$/.test(value.adapterModuleSha256)
941
1086
  || !value.probeModulePath || !/^[a-f0-9]{64}$/.test(value.probeModuleSha256)
@@ -0,0 +1,49 @@
1
+ import type { SandboxCertificationHarness } from "./sandbox-harness.js";
2
+ import { type ShadowCandidateEvaluation, type TaskCandidateEvaluation, type TaskEvaluationContract, type TaskEvaluationScenario } from "./task-evaluation.js";
3
+ export declare const BUSINESS_TASK_EVALUATOR_SCHEMA_VERSION: "witnora.business_task_evaluator.v0.1";
4
+ export declare const BUSINESS_TASK_EVALUATION_REQUEST_SCHEMA_VERSION: "witnora.business_task_evaluation_request.v0.1";
5
+ export interface BusinessTaskEvaluationBinding {
6
+ nodeId: string;
7
+ taskContractId: string;
8
+ taskContractDigestSha256: string;
9
+ taskKey: string;
10
+ agentId: string;
11
+ agentVersion: string;
12
+ mode: "REPLAY" | "SHADOW";
13
+ }
14
+ export interface BusinessTaskEvaluatorServerOptions {
15
+ credential: string;
16
+ contractSha256: string;
17
+ host?: "127.0.0.1" | "::1";
18
+ port?: number;
19
+ maxBodyBytes?: number;
20
+ maxConcurrency?: number;
21
+ evaluationTimeoutMs?: number;
22
+ loadReplayScenarios?(task: TaskEvaluationContract, binding: BusinessTaskEvaluationBinding, signal: AbortSignal): Promise<TaskEvaluationScenario[]>;
23
+ createReplayHarness?(task: TaskEvaluationContract, binding: BusinessTaskEvaluationBinding, signal: AbortSignal): Promise<{
24
+ harness: SandboxCertificationHarness;
25
+ tenantId: string;
26
+ }>;
27
+ evaluateReplayCandidate?(scenario: {
28
+ id: string;
29
+ source: TaskEvaluationScenario["source"];
30
+ input: unknown;
31
+ }, task: TaskEvaluationContract, binding: BusinessTaskEvaluationBinding, signal: AbortSignal): Promise<TaskCandidateEvaluation>;
32
+ loadShadowObservations?(task: TaskEvaluationContract, binding: BusinessTaskEvaluationBinding, signal: AbortSignal): Promise<TaskEvaluationScenario[]>;
33
+ evaluateShadowCandidate?(context: {
34
+ id: string;
35
+ source: "LIVE_SHADOW";
36
+ input: unknown;
37
+ propose: (intent: {
38
+ pathId: string;
39
+ parametersDigestSha256: string;
40
+ }) => void;
41
+ }, task: TaskEvaluationContract, binding: BusinessTaskEvaluationBinding, signal: AbortSignal): Promise<ShadowCandidateEvaluation>;
42
+ }
43
+ export declare function createBusinessTaskEvaluatorServer(options: BusinessTaskEvaluatorServerOptions): {
44
+ start(): Promise<{
45
+ origin: string;
46
+ }>;
47
+ close(): Promise<void>;
48
+ };
49
+ //# sourceMappingURL=business-task-evaluator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"business-task-evaluator.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/business-task-evaluator.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAE3B,KAAK,sBAAsB,EAC5B,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,sCAAsC,EAAG,sCAA+C,CAAC;AACtG,eAAO,MAAM,+CAA+C,EAAG,+CAAwD,CAAC;AAExH,MAAM,WAAW,6BAA6B;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB,EAAE,MAAM,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC3B;AAED,MAAM,WAAW,kCAAkC;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,CAAC,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACnJ,mBAAmB,CAAC,CAAC,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,2BAA2B,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrL,uBAAuB,CAAC,CAAC,QAAQ,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC1O,sBAAsB,CAAC,CAAC,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,sBAAsB,EAAE,CAAC,CAAC;IACtJ,uBAAuB,CAAC,CAAC,OAAO,EAAE;QAChC,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,aAAa,CAAC;QACtB,KAAK,EAAE,OAAO,CAAC;QACf,OAAO,EAAE,CAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,sBAAsB,EAAE,MAAM,CAAA;SAAE,KAAK,IAAI,CAAC;KAC/E,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACnI;AAED,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,kCAAkC;aA+C1E,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;aAQ3B,OAAO,CAAC,IAAI,CAAC;EAK/B"}
@@ -0,0 +1,185 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ import { runReplayEvaluation, runShadowEvaluation, } from "./task-evaluation.js";
4
+ export const BUSINESS_TASK_EVALUATOR_SCHEMA_VERSION = "witnora.business_task_evaluator.v0.1";
5
+ export const BUSINESS_TASK_EVALUATION_REQUEST_SCHEMA_VERSION = "witnora.business_task_evaluation_request.v0.1";
6
+ export function createBusinessTaskEvaluatorServer(options) {
7
+ const host = options.host ?? "127.0.0.1";
8
+ if (host !== "127.0.0.1" && host !== "::1")
9
+ throw new Error("Business Task Evaluator accepts only a literal loopback host.");
10
+ const credential = required(options.credential, "credential");
11
+ const contractSha256 = digest(options.contractSha256, "contractSha256");
12
+ const port = bounded(options.port ?? 0, 0, 65_535, "port");
13
+ const maxBodyBytes = bounded(options.maxBodyBytes ?? 1_048_576, 1_024, 4_194_304, "maxBodyBytes");
14
+ const maxConcurrency = bounded(options.maxConcurrency ?? 4, 1, 16, "maxConcurrency");
15
+ const evaluationTimeoutMs = bounded(options.evaluationTimeoutMs ?? 120_000, 1_000, 600_000, "evaluationTimeoutMs");
16
+ const modes = [
17
+ ...(options.loadReplayScenarios && options.createReplayHarness && options.evaluateReplayCandidate ? ["REPLAY"] : []),
18
+ ...(options.loadShadowObservations && options.evaluateShadowCandidate ? ["SHADOW"] : []),
19
+ ];
20
+ let active = 0;
21
+ const server = createServer(async (request, response) => {
22
+ response.setHeader("cache-control", "no-store");
23
+ response.setHeader("content-type", "application/json; charset=utf-8");
24
+ try {
25
+ if (!authorized(request, credential))
26
+ return send(response, 401, { error: "unauthorized" });
27
+ if (request.method === "GET" && request.url === "/healthz") {
28
+ return send(response, 200, {
29
+ schemaVersion: BUSINESS_TASK_EVALUATOR_SCHEMA_VERSION,
30
+ contractSha256,
31
+ ready: modes.length > 0,
32
+ modes,
33
+ boundary: { host, productionWrites: 0, rawInputsRetainedByCustomer: true },
34
+ });
35
+ }
36
+ if (request.method !== "POST" || request.url !== "/v1/evaluate")
37
+ return send(response, 404, { error: "not_found" });
38
+ if (!String(request.headers["content-type"] ?? "").toLowerCase().startsWith("application/json")) {
39
+ return send(response, 415, { error: "json_required" });
40
+ }
41
+ if (active >= maxConcurrency)
42
+ return send(response, 429, { error: "evaluator_busy" });
43
+ const body = await readJson(request, maxBodyBytes);
44
+ const parsed = parseRequest(body);
45
+ validateBinding(parsed.task, parsed.evaluation);
46
+ active += 1;
47
+ try {
48
+ const report = await withTimeout((signal) => evaluate(options, parsed.task, parsed.evaluation, signal), evaluationTimeoutMs);
49
+ return send(response, 200, { schemaVersion: BUSINESS_TASK_EVALUATOR_SCHEMA_VERSION, report });
50
+ }
51
+ finally {
52
+ active -= 1;
53
+ }
54
+ }
55
+ catch (error) {
56
+ const status = error instanceof EvaluatorError ? error.status : 422;
57
+ return send(response, status, { error: error instanceof EvaluatorError ? error.code : "evaluation_failed" });
58
+ }
59
+ });
60
+ return {
61
+ async start() {
62
+ if (!server.listening)
63
+ await new Promise((resolve, reject) => {
64
+ server.once("error", reject);
65
+ server.listen(port, host, () => { server.off("error", reject); resolve(); });
66
+ });
67
+ const address = server.address();
68
+ return { origin: `http://${host === "::1" ? "[::1]" : host}:${address.port}` };
69
+ },
70
+ async close() {
71
+ if (!server.listening)
72
+ return;
73
+ await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
74
+ },
75
+ };
76
+ }
77
+ async function evaluate(options, task, binding, signal) {
78
+ if (binding.mode === "REPLAY") {
79
+ if (!options.loadReplayScenarios || !options.createReplayHarness || !options.evaluateReplayCandidate) {
80
+ throw new EvaluatorError(409, "replay_not_configured");
81
+ }
82
+ const [{ harness, tenantId }, scenarios] = await Promise.all([
83
+ options.createReplayHarness(task, binding, signal), options.loadReplayScenarios(task, binding, signal),
84
+ ]);
85
+ return runReplayEvaluation({
86
+ task, harness, tenantId, scenarios,
87
+ evaluateCandidate: (scenario) => options.evaluateReplayCandidate(scenario, task, binding, signal),
88
+ });
89
+ }
90
+ if (!options.loadShadowObservations || !options.evaluateShadowCandidate)
91
+ throw new EvaluatorError(409, "shadow_not_configured");
92
+ return runShadowEvaluation({
93
+ task,
94
+ observations: await options.loadShadowObservations(task, binding, signal),
95
+ evaluateCandidate: (context) => options.evaluateShadowCandidate(context, task, binding, signal),
96
+ });
97
+ }
98
+ function parseRequest(input) {
99
+ const body = object(input, "request");
100
+ exactKeys(body, ["schemaVersion", "task", "evaluation"], "request");
101
+ if (body.schemaVersion !== BUSINESS_TASK_EVALUATION_REQUEST_SCHEMA_VERSION)
102
+ throw new EvaluatorError(422, "invalid_schema");
103
+ const evaluation = object(body.evaluation, "evaluation");
104
+ exactKeys(evaluation, ["nodeId", "taskContractId", "taskContractDigestSha256", "taskKey", "agentId", "agentVersion", "mode"], "evaluation");
105
+ if (evaluation.mode !== "REPLAY" && evaluation.mode !== "SHADOW")
106
+ throw new EvaluatorError(422, "invalid_mode");
107
+ const binding = {
108
+ nodeId: requiredString(evaluation.nodeId), taskContractId: requiredString(evaluation.taskContractId),
109
+ taskContractDigestSha256: digest(String(evaluation.taskContractDigestSha256 ?? ""), "taskContractDigestSha256"),
110
+ taskKey: requiredString(evaluation.taskKey), agentId: requiredString(evaluation.agentId),
111
+ agentVersion: requiredString(evaluation.agentVersion), mode: evaluation.mode,
112
+ };
113
+ return { task: object(body.task, "task"), evaluation: binding };
114
+ }
115
+ function validateBinding(task, binding) {
116
+ if (task.id !== binding.taskContractId || task.digestSha256 !== binding.taskContractDigestSha256
117
+ || task.taskKey !== binding.taskKey || task.subject?.agentId !== binding.agentId
118
+ || task.subject?.agentVersion !== binding.agentVersion)
119
+ throw new EvaluatorError(409, "task_binding_mismatch");
120
+ }
121
+ function authorized(request, credential) {
122
+ const supplied = String(request.headers.authorization ?? "").replace(/^Bearer\s+/i, "");
123
+ const left = Buffer.from(supplied);
124
+ const right = Buffer.from(credential);
125
+ return left.length === right.length && left.length > 0 && timingSafeEqual(left, right);
126
+ }
127
+ async function readJson(request, limit) {
128
+ const chunks = [];
129
+ let size = 0;
130
+ for await (const chunk of request) {
131
+ const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
132
+ size += value.length;
133
+ if (size > limit)
134
+ throw new EvaluatorError(413, "body_too_large");
135
+ chunks.push(value);
136
+ }
137
+ try {
138
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
139
+ }
140
+ catch {
141
+ throw new EvaluatorError(400, "invalid_json");
142
+ }
143
+ }
144
+ async function withTimeout(execute, timeoutMs) {
145
+ const controller = new AbortController();
146
+ let timer;
147
+ try {
148
+ return await Promise.race([execute(controller.signal), new Promise((_, reject) => {
149
+ timer = setTimeout(() => { controller.abort(); reject(new EvaluatorError(504, "evaluation_timeout")); }, timeoutMs);
150
+ timer.unref?.();
151
+ })]);
152
+ }
153
+ finally {
154
+ if (timer)
155
+ clearTimeout(timer);
156
+ }
157
+ }
158
+ function send(response, status, body) { response.statusCode = status; response.end(JSON.stringify(body)); }
159
+ function object(value, name) {
160
+ if (!value || typeof value !== "object" || Array.isArray(value))
161
+ throw new EvaluatorError(422, `invalid_${name}`);
162
+ return value;
163
+ }
164
+ function exactKeys(value, allowed, name) {
165
+ const keys = new Set(allowed);
166
+ if (Object.keys(value).some((key) => !keys.has(key)))
167
+ throw new EvaluatorError(422, `unexpected_${name}_field`);
168
+ }
169
+ function required(value, name) { const result = value.trim(); if (!result)
170
+ throw new Error(`${name} is required.`); return result; }
171
+ function requiredString(value) { if (typeof value !== "string" || !value.trim())
172
+ throw new EvaluatorError(422, "invalid_binding"); return value.trim(); }
173
+ function digest(value, name) { if (!/^[a-f0-9]{64}$/.test(value))
174
+ throw new Error(`${name} must be a lowercase SHA-256 digest.`); return value; }
175
+ function bounded(value, min, max, name) { if (!Number.isInteger(value) || value < min || value > max)
176
+ throw new Error(`${name} must be between ${min} and ${max}.`); return value; }
177
+ class EvaluatorError extends Error {
178
+ status;
179
+ code;
180
+ constructor(status, code) {
181
+ super(code);
182
+ this.status = status;
183
+ this.code = code;
184
+ }
185
+ }
@@ -0,0 +1,92 @@
1
+ import type { TaskEvaluationContract, TaskEvaluationReport } from "./task-evaluation.js";
2
+ export type ManagedWorkflowEvaluationMode = "REPLAY" | "SHADOW";
3
+ export interface ManagedWorkflowExecutionPlan {
4
+ schemaVersion: "witnora.business_workflow_execution_plan.v0.1";
5
+ projectId: string;
6
+ generatedAt: string;
7
+ workflow: {
8
+ id: string;
9
+ digestSha256: string;
10
+ status: "ACTIVE" | "RETIRED";
11
+ };
12
+ nodes: Array<{
13
+ id: string;
14
+ status: "BLOCKED" | "READY" | "RUNNING" | "NEEDS_DECISION" | "COMPLETED";
15
+ }>;
16
+ readyEvaluations: ManagedWorkflowReadyEvaluation[];
17
+ recordsTruncated: boolean;
18
+ safety: {
19
+ productionWrites: 0;
20
+ aggregateAssuranceClaim: false;
21
+ customerHarnessRequired: true;
22
+ };
23
+ limitations: string[];
24
+ }
25
+ export interface ManagedWorkflowReadyEvaluation {
26
+ nodeId: string;
27
+ taskContractId: string;
28
+ taskContractDigestSha256: string;
29
+ taskKey: string;
30
+ agentId: string;
31
+ agentVersion: string;
32
+ mode: ManagedWorkflowEvaluationMode;
33
+ }
34
+ export interface ManagedWorkflowHostedClient {
35
+ listActiveWorkflows(): Promise<Array<{
36
+ id: string;
37
+ digestSha256: string;
38
+ status: "ACTIVE" | "RETIRED";
39
+ }>>;
40
+ getExecutionPlan(workflowId: string): Promise<ManagedWorkflowExecutionPlan>;
41
+ getTask(taskContractId: string): Promise<TaskEvaluationContract>;
42
+ upload(report: TaskEvaluationReport, input: {
43
+ externalId: string;
44
+ }): Promise<unknown>;
45
+ }
46
+ export declare function createManagedWorkflowHostedClient(options: {
47
+ baseUrl: string;
48
+ projectId: string;
49
+ apiKey: string;
50
+ workflowIds?: string[];
51
+ fetch?: typeof fetch;
52
+ }): ManagedWorkflowHostedClient;
53
+ export interface WorkflowEvaluationCheckpointStore {
54
+ load(key: string): Promise<TaskEvaluationReport | undefined>;
55
+ save(key: string, report: TaskEvaluationReport): Promise<void>;
56
+ complete(key: string): Promise<void>;
57
+ }
58
+ export declare class MemoryWorkflowEvaluationCheckpointStore implements WorkflowEvaluationCheckpointStore {
59
+ #private;
60
+ load(key: string): Promise<TaskEvaluationReport | undefined>;
61
+ save(key: string, report: TaskEvaluationReport): Promise<void>;
62
+ complete(key: string): Promise<void>;
63
+ }
64
+ export declare class FileWorkflowEvaluationCheckpointStore implements WorkflowEvaluationCheckpointStore {
65
+ private readonly directory;
66
+ constructor(directory: string);
67
+ load(key: string): Promise<TaskEvaluationReport | undefined>;
68
+ save(key: string, report: TaskEvaluationReport): Promise<void>;
69
+ complete(key: string): Promise<void>;
70
+ private path;
71
+ }
72
+ export interface ManagedBusinessWorkflowHarnessResult {
73
+ workflowsInspected: number;
74
+ evaluationsCompleted: number;
75
+ evaluationsFailed: number;
76
+ limitations: string[];
77
+ }
78
+ export declare class ManagedBusinessWorkflowHarness {
79
+ #private;
80
+ constructor(options: {
81
+ client: ManagedWorkflowHostedClient;
82
+ checkpoints: WorkflowEvaluationCheckpointStore;
83
+ evaluate: (input: {
84
+ task: TaskEvaluationContract;
85
+ evaluation: ManagedWorkflowReadyEvaluation;
86
+ }) => Promise<TaskEvaluationReport>;
87
+ maxConcurrency?: number;
88
+ });
89
+ tick(): Promise<ManagedBusinessWorkflowHarnessResult>;
90
+ private runTick;
91
+ }
92
+ //# sourceMappingURL=managed-workflow-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"managed-workflow-harness.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/managed-workflow-harness.ts"],"names":[],"mappings":"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"}