witnora 0.13.6 → 0.13.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ import type { SandboxCertificationHarness } from "./sandbox-harness.js";
2
+ import type { CreateActionIntentInput } from "./types.js";
3
+ export declare const TASK_EVALUATION_SCHEMA_VERSION: "witnora.task_evaluation.v0.1";
4
+ export interface TaskEvaluationContract {
5
+ id: string;
6
+ digestSha256: string;
7
+ taskKey: string;
8
+ version: number;
9
+ subject: {
10
+ agentId: string;
11
+ agentVersion: string;
12
+ };
13
+ scenarioSuite: {
14
+ id: string;
15
+ version: string;
16
+ sha256: string;
17
+ };
18
+ actionPaths: Array<{
19
+ id: string;
20
+ actionType: "SUBMIT" | "PAY" | "SEND" | "UPDATE";
21
+ targetSystem: string;
22
+ requiredPermissions: string[];
23
+ customerDecision?: "ALLOW" | "REQUIRE_APPROVAL" | "DENY";
24
+ }>;
25
+ successCriteria: Array<{
26
+ id: string;
27
+ evaluator: "DETERMINISTIC" | "READ_ONLY_PROBE";
28
+ predicateDigestSha256: string;
29
+ }>;
30
+ }
31
+ export interface TaskEvaluationIntent {
32
+ pathId: string;
33
+ parametersDigestSha256: string;
34
+ action?: CreateActionIntentInput;
35
+ }
36
+ export interface TaskEvaluationScenario {
37
+ id: string;
38
+ source: "HISTORICAL" | "FIXTURE" | "LIVE_SHADOW";
39
+ sanitized: boolean;
40
+ input: unknown;
41
+ inputDigestSha256: string;
42
+ baseline: {
43
+ resultDigestSha256: string;
44
+ actionIntents: Array<Omit<TaskEvaluationIntent, "action">>;
45
+ };
46
+ }
47
+ export interface TaskCandidateEvaluation {
48
+ resultDigestSha256: string;
49
+ actionIntents: TaskEvaluationIntent[];
50
+ criteria: Array<{
51
+ id: string;
52
+ passed: boolean;
53
+ }>;
54
+ }
55
+ export interface ShadowCandidateEvaluation {
56
+ resultDigestSha256: string;
57
+ criteria: Array<{
58
+ id: string;
59
+ passed: boolean;
60
+ }>;
61
+ }
62
+ export interface TaskEvaluationReport {
63
+ schemaVersion: typeof TASK_EVALUATION_SCHEMA_VERSION;
64
+ mode: "REPLAY" | "SHADOW";
65
+ classification: "REPLAY_SANDBOX_ONLY" | "SHADOW_NO_WRITE";
66
+ generatedAt: string;
67
+ task: {
68
+ id: string;
69
+ digestSha256: string;
70
+ taskKey: string;
71
+ version: number;
72
+ subject: TaskEvaluationContract["subject"];
73
+ scenarioSuite: TaskEvaluationContract["scenarioSuite"];
74
+ };
75
+ verdict: "PASSED" | "MATCHED" | "CHANGED" | "BLOCKED";
76
+ summary: {
77
+ scenarios: number;
78
+ matched: number;
79
+ changed: number;
80
+ blocked: number;
81
+ sandboxActions: number;
82
+ productionWrites: 0;
83
+ };
84
+ scenarios: Array<{
85
+ id: string;
86
+ source: TaskEvaluationScenario["source"];
87
+ inputDigestSha256: string;
88
+ matched: boolean;
89
+ blocked: boolean;
90
+ sandboxActionCount: number;
91
+ criteria: Array<{
92
+ id: string;
93
+ status: "PASSED" | "FAILED" | "NOT_OBSERVED";
94
+ evaluator: "DETERMINISTIC" | "READ_ONLY_PROBE";
95
+ }>;
96
+ differences: string[];
97
+ }>;
98
+ limitations: string[];
99
+ }
100
+ export declare function runReplayEvaluation(options: {
101
+ task: TaskEvaluationContract;
102
+ harness: SandboxCertificationHarness;
103
+ tenantId: string;
104
+ scenarios: TaskEvaluationScenario[];
105
+ evaluateCandidate: (scenario: {
106
+ id: string;
107
+ source: TaskEvaluationScenario["source"];
108
+ input: unknown;
109
+ }) => Promise<TaskCandidateEvaluation>;
110
+ now?: () => Date;
111
+ }): Promise<TaskEvaluationReport>;
112
+ export declare function runShadowEvaluation(options: {
113
+ task: TaskEvaluationContract;
114
+ observations: TaskEvaluationScenario[];
115
+ evaluateCandidate: (context: {
116
+ id: string;
117
+ source: "LIVE_SHADOW";
118
+ input: unknown;
119
+ propose: (intent: Omit<TaskEvaluationIntent, "action">) => void;
120
+ }) => Promise<ShadowCandidateEvaluation>;
121
+ now?: () => Date;
122
+ }): Promise<TaskEvaluationReport>;
123
+ //# sourceMappingURL=task-evaluation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-evaluation.d.ts","sourceRoot":"","sources":["../../../../onegent-runtime/src/task-evaluation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,206 @@
1
+ export const TASK_EVALUATION_SCHEMA_VERSION = "witnora.task_evaluation.v0.1";
2
+ export async function runReplayEvaluation(options) {
3
+ validateTask(options.task);
4
+ validateScenarios(options.scenarios, false);
5
+ const now = options.now ?? (() => new Date());
6
+ const run = await options.harness.startRun({ tenantId: options.tenantId });
7
+ const results = [];
8
+ for (const scenario of options.scenarios) {
9
+ await options.harness.resetTenant(options.tenantId);
10
+ const candidate = await options.evaluateCandidate({ id: scenario.id, source: scenario.source, input: scenario.input });
11
+ validateCandidate(candidate, options.task);
12
+ const differences = compareCandidate(scenario, candidate);
13
+ let blocked = false;
14
+ let sandboxActionCount = 0;
15
+ for (const intent of candidate.actionIntents) {
16
+ const path = options.task.actionPaths.find((item) => item.id === intent.pathId);
17
+ if (!path || !intent.action || !actionMatchesPath(intent.action, path)) {
18
+ blocked = true;
19
+ differences.push(`Action path ${intent.pathId} was not bound to the approved sandbox contract.`);
20
+ continue;
21
+ }
22
+ const execution = await run.executeAction(intent.action, {
23
+ approval: { approved: true, reviewerId: `business-task:${options.task.id}`, comment: "Approved synthetic Replay path from the customer Business Task Contract." },
24
+ rollbackAfterVerification: true,
25
+ rollbackReason: "Replay restores the synthetic tenant after each observed action.",
26
+ });
27
+ if (execution.status !== "verified" && execution.status !== "rolled_back") {
28
+ blocked = true;
29
+ differences.push(`Sandbox action ${intent.pathId} was ${execution.status}${execution.rejectionCode ? ` (${execution.rejectionCode})` : ""}.`);
30
+ }
31
+ else {
32
+ sandboxActionCount += 1;
33
+ }
34
+ }
35
+ const criteria = options.task.successCriteria.map((criterion) => ({
36
+ id: criterion.id,
37
+ evaluator: criterion.evaluator,
38
+ status: candidate.criteria.find((item) => item.id === criterion.id)?.passed === true ? "PASSED" : "FAILED",
39
+ }));
40
+ if (criteria.some((item) => item.status !== "PASSED"))
41
+ differences.push("One or more declared success criteria did not pass.");
42
+ const matched = !blocked && differences.length === 0;
43
+ results.push({
44
+ id: scenario.id,
45
+ source: scenario.source,
46
+ inputDigestSha256: scenario.inputDigestSha256,
47
+ matched,
48
+ blocked,
49
+ sandboxActionCount,
50
+ criteria,
51
+ differences: [...new Set(differences)],
52
+ });
53
+ }
54
+ run.complete();
55
+ return report("REPLAY", "REPLAY_SANDBOX_ONLY", options.task, results, now());
56
+ }
57
+ export async function runShadowEvaluation(options) {
58
+ validateTask(options.task);
59
+ validateScenarios(options.observations, true);
60
+ const now = options.now ?? (() => new Date());
61
+ const results = [];
62
+ for (const observation of options.observations) {
63
+ const proposals = [];
64
+ const candidate = await options.evaluateCandidate({
65
+ id: observation.id,
66
+ source: "LIVE_SHADOW",
67
+ input: observation.input,
68
+ propose: (intent) => {
69
+ digest(intent.parametersDigestSha256, `candidate action ${intent.pathId} digest`);
70
+ if (!options.task.actionPaths.some((path) => path.id === intent.pathId)) {
71
+ throw new Error(`Candidate proposed undeclared action path ${intent.pathId}.`);
72
+ }
73
+ proposals.push(structuredClone(intent));
74
+ },
75
+ });
76
+ validateShadowCandidate(candidate, options.task);
77
+ const candidateForComparison = {
78
+ resultDigestSha256: candidate.resultDigestSha256,
79
+ actionIntents: proposals,
80
+ criteria: candidate.criteria,
81
+ };
82
+ const differences = compareCandidate(observation, candidateForComparison);
83
+ const criteria = options.task.successCriteria.map((criterion) => ({
84
+ id: criterion.id,
85
+ evaluator: criterion.evaluator,
86
+ status: criterion.evaluator === "READ_ONLY_PROBE"
87
+ ? "NOT_OBSERVED"
88
+ : candidate.criteria.find((item) => item.id === criterion.id)?.passed === true
89
+ ? "PASSED"
90
+ : "FAILED",
91
+ }));
92
+ if (criteria.some((item) => item.status === "FAILED"))
93
+ differences.push("One or more deterministic success criteria did not pass.");
94
+ results.push({
95
+ id: observation.id,
96
+ source: observation.source,
97
+ inputDigestSha256: observation.inputDigestSha256,
98
+ matched: differences.length === 0,
99
+ blocked: false,
100
+ sandboxActionCount: 0,
101
+ criteria,
102
+ differences: [...new Set(differences)],
103
+ });
104
+ }
105
+ return report("SHADOW", "SHADOW_NO_WRITE", options.task, results, now());
106
+ }
107
+ function report(mode, classification, task, scenarios, generatedAt) {
108
+ const blocked = scenarios.filter((item) => item.blocked).length;
109
+ const matched = scenarios.filter((item) => item.matched).length;
110
+ const changed = scenarios.length - matched - blocked;
111
+ return {
112
+ schemaVersion: TASK_EVALUATION_SCHEMA_VERSION,
113
+ mode,
114
+ classification,
115
+ generatedAt: generatedAt.toISOString(),
116
+ task: {
117
+ id: task.id,
118
+ digestSha256: task.digestSha256,
119
+ taskKey: task.taskKey,
120
+ version: task.version,
121
+ subject: structuredClone(task.subject),
122
+ scenarioSuite: structuredClone(task.scenarioSuite),
123
+ },
124
+ verdict: blocked ? "BLOCKED" : changed ? "CHANGED" : mode === "SHADOW" ? "MATCHED" : "PASSED",
125
+ summary: {
126
+ scenarios: scenarios.length,
127
+ matched,
128
+ changed,
129
+ blocked,
130
+ sandboxActions: scenarios.reduce((sum, item) => sum + item.sandboxActionCount, 0),
131
+ productionWrites: 0,
132
+ },
133
+ scenarios,
134
+ limitations: mode === "REPLAY"
135
+ ? [
136
+ "Replay covers only the declared scenarios and network-denied synthetic or sanitized sandbox paths.",
137
+ "Replay results do not establish production enforcement, production outcome verification, or future Agent reliability.",
138
+ ]
139
+ : [
140
+ "Shadow compares candidate decisions against observed inputs while candidate writes remain blocked.",
141
+ "Shadow results do not establish that the candidate produced a real production outcome.",
142
+ ],
143
+ };
144
+ }
145
+ function compareCandidate(scenario, candidate) {
146
+ const differences = [];
147
+ if (candidate.resultDigestSha256 !== scenario.baseline.resultDigestSha256)
148
+ differences.push("Candidate result digest differs from the baseline.");
149
+ const expected = scenario.baseline.actionIntents.map(intentFingerprint).sort();
150
+ const observed = candidate.actionIntents.map(intentFingerprint).sort();
151
+ if (JSON.stringify(expected) !== JSON.stringify(observed))
152
+ differences.push("Candidate action intents differ from the baseline.");
153
+ return differences;
154
+ }
155
+ function intentFingerprint(intent) {
156
+ return `${intent.pathId}:${intent.parametersDigestSha256}`;
157
+ }
158
+ function actionMatchesPath(action, path) {
159
+ const permissions = [...new Set(action.requestedPermissions ?? [])].sort();
160
+ return path.customerDecision !== "DENY"
161
+ && action.environment !== "production"
162
+ && !action.targetUrl
163
+ && action.actionType === path.actionType
164
+ && action.targetSystem === path.targetSystem
165
+ && JSON.stringify(permissions) === JSON.stringify([...new Set(path.requiredPermissions)].sort());
166
+ }
167
+ function validateTask(task) {
168
+ digest(task.digestSha256, "task.digestSha256");
169
+ digest(task.scenarioSuite.sha256, "task.scenarioSuite.sha256");
170
+ if (!task.actionPaths.length || !task.successCriteria.length)
171
+ throw new Error("Task evaluation requires action paths and success criteria.");
172
+ for (const criterion of task.successCriteria)
173
+ digest(criterion.predicateDigestSha256, `criterion ${criterion.id}`);
174
+ }
175
+ function validateScenarios(scenarios, shadow) {
176
+ if (!scenarios.length || scenarios.length > 500)
177
+ throw new Error("Task evaluation requires 1 to 500 scenarios.");
178
+ for (const scenario of scenarios) {
179
+ if (!shadow && scenario.source === "LIVE_SHADOW")
180
+ throw new Error("Replay cannot consume a LIVE_SHADOW scenario.");
181
+ if (shadow && scenario.source !== "LIVE_SHADOW")
182
+ throw new Error("Shadow requires LIVE_SHADOW observations.");
183
+ if (scenario.sanitized !== true)
184
+ throw new Error(`Scenario ${scenario.id} must be explicitly sanitized.`);
185
+ digest(scenario.inputDigestSha256, `scenario ${scenario.id} input digest`);
186
+ digest(scenario.baseline.resultDigestSha256, `scenario ${scenario.id} baseline result digest`);
187
+ scenario.baseline.actionIntents.forEach((intent) => digest(intent.parametersDigestSha256, `scenario ${scenario.id} baseline action digest`));
188
+ }
189
+ }
190
+ function validateShadowCandidate(candidate, task) {
191
+ digest(candidate.resultDigestSha256, "candidate result digest");
192
+ const criterionIds = new Set(task.successCriteria.map((item) => item.id));
193
+ if (candidate.criteria.some((item) => !criterionIds.has(item.id)))
194
+ throw new Error("Candidate returned an undeclared success criterion.");
195
+ }
196
+ function validateCandidate(candidate, task) {
197
+ digest(candidate.resultDigestSha256, "candidate result digest");
198
+ candidate.actionIntents.forEach((intent) => digest(intent.parametersDigestSha256, `candidate action ${intent.pathId} digest`));
199
+ const criterionIds = new Set(task.successCriteria.map((item) => item.id));
200
+ if (candidate.criteria.some((item) => !criterionIds.has(item.id)))
201
+ throw new Error("Candidate returned an undeclared success criterion.");
202
+ }
203
+ function digest(value, name) {
204
+ if (!/^[a-f0-9]{64}$/.test(value))
205
+ throw new Error(`${name} must be a lowercase SHA-256 digest.`);
206
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "witnora",
3
- "version": "0.13.6",
3
+ "version": "0.13.8",
4
4
  "description": "Independent assurance for covered agent action paths across models and frameworks.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",