witnora 0.9.3 → 0.10.2

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.
Files changed (77) hide show
  1. package/README.md +76 -9
  2. package/dist/assurance-contract.js +312 -0
  3. package/dist/assurance-loop-demo.js +245 -0
  4. package/dist/authorization-state-machine.js +35 -0
  5. package/dist/authorization-v02.js +60 -0
  6. package/dist/bundle.js +22 -0
  7. package/dist/canonical-v02.js +197 -0
  8. package/dist/canonical.js +31 -0
  9. package/dist/cli.js +132 -0
  10. package/dist/command-help.js +81 -0
  11. package/dist/control-plane.js +12 -3
  12. package/dist/credentials.js +1 -1
  13. package/dist/design-partner-pilot.js +402 -0
  14. package/dist/design-partner-v02.js +449 -0
  15. package/dist/evidence-v02.js +118 -0
  16. package/dist/generic-eval.js +160 -0
  17. package/dist/github-design-partner-v02.js +399 -0
  18. package/dist/github-live-v02.js +45 -0
  19. package/dist/guided-setup.js +1 -1
  20. package/dist/index.js +1 -0
  21. package/dist/offline-verifier.js +375 -0
  22. package/dist/onboard.js +132 -0
  23. package/dist/onboarding-templates.js +49 -6
  24. package/dist/outcome-evaluator.js +51 -0
  25. package/dist/privacy-manifest.js +273 -0
  26. package/dist/probe-protocol-v02.js +101 -0
  27. package/dist/runtime-context.js +75 -0
  28. package/dist/sandbox.js +1 -1
  29. package/dist/schema-validator.js +141 -3
  30. package/dist/trust-v02.js +111 -0
  31. package/dist/try.js +1 -1
  32. package/dist/vendor/design-partner/failure-exercises.mjs +353 -0
  33. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts +8 -0
  34. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts.map +1 -1
  35. package/dist/vendor/onegent-runtime/browser-enforcement-runtime.js +43 -1
  36. package/dist/vendor/witnora-probe/canonical.d.ts +3 -0
  37. package/dist/vendor/witnora-probe/canonical.js +39 -0
  38. package/dist/vendor/witnora-probe/cli.d.ts +2 -0
  39. package/dist/vendor/witnora-probe/cli.js +36 -0
  40. package/dist/vendor/witnora-probe/config.d.ts +2 -0
  41. package/dist/vendor/witnora-probe/config.js +63 -0
  42. package/dist/vendor/witnora-probe/credential-resolver.d.ts +6 -0
  43. package/dist/vendor/witnora-probe/credential-resolver.js +17 -0
  44. package/dist/vendor/witnora-probe/crypto.d.ts +2 -0
  45. package/dist/vendor/witnora-probe/crypto.js +14 -0
  46. package/dist/vendor/witnora-probe/emulator.d.ts +51 -0
  47. package/dist/vendor/witnora-probe/emulator.js +164 -0
  48. package/dist/vendor/witnora-probe/index.d.ts +11 -0
  49. package/dist/vendor/witnora-probe/index.js +11 -0
  50. package/dist/vendor/witnora-probe/logger.d.ts +3 -0
  51. package/dist/vendor/witnora-probe/logger.js +26 -0
  52. package/dist/vendor/witnora-probe/metrics.d.ts +12 -0
  53. package/dist/vendor/witnora-probe/metrics.js +31 -0
  54. package/dist/vendor/witnora-probe/probe.d.ts +22 -0
  55. package/dist/vendor/witnora-probe/probe.js +208 -0
  56. package/dist/vendor/witnora-probe/request-verifier.d.ts +9 -0
  57. package/dist/vendor/witnora-probe/request-verifier.js +84 -0
  58. package/dist/vendor/witnora-probe/server.d.ts +9 -0
  59. package/dist/vendor/witnora-probe/server.js +85 -0
  60. package/dist/vendor/witnora-probe/storage.d.ts +16 -0
  61. package/dist/vendor/witnora-probe/storage.js +114 -0
  62. package/dist/vendor/witnora-probe/types.d.ts +126 -0
  63. package/dist/vendor/witnora-probe/types.js +2 -0
  64. package/dist/vendor/witnora-verifier-python/README.md +43 -0
  65. package/dist/vendor/witnora-verifier-python/pyproject.toml +23 -0
  66. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/__init__.py +33 -0
  67. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/canonical.py +218 -0
  68. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/cli.py +75 -0
  69. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/conformance.py +124 -0
  70. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/crypto.py +139 -0
  71. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/errors.py +15 -0
  72. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/event_chain.py +80 -0
  73. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/evidence.py +336 -0
  74. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/probe.py +188 -0
  75. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/producer.py +127 -0
  76. package/dist/vendor/witnora-verifier-python/src/witnora_verifier/trust.py +337 -0
  77. package/package.json +5 -5
package/README.md CHANGED
@@ -9,17 +9,51 @@ outcome. It writes portable reports and accumulates a local failure corpus.
9
9
  [Public evidence](https://witnora.com/evidence) |
10
10
  [Get started](https://witnora.com/docs) |
11
11
  [Private workspace](https://witnora.com/app) |
12
- [GitHub source](https://github.com/Kakarottoooo/agentcert)
12
+ [GitHub source](https://github.com/Kakarottoooo/witnora)
13
13
 
14
- ## 5-minute local path
14
+ ## 5-minute hosted path
15
15
 
16
- No agent repository or API key is required for the first run:
16
+ Create a Hosted project, open a terminal in the agent repository, and run the
17
+ one command shown by Witnora:
17
18
 
18
19
  ```bash
19
- npx witnora@latest try --template workflow
20
+ npx witnora@latest onboard --project your-project-id
20
21
  ```
21
22
 
22
- Inspect `.witnora/try/agentcert-report.html` and
23
+ The browser approval creates a restricted project credential without exposing
24
+ it in Hosted, then the CLI saves it outside the repository, detects the agent
25
+ boundary, writes only missing configuration, and verifies the local evidence
26
+ path. `.witnora/onboarding/receipt.json` is synthetic: it cannot create a run,
27
+ evidence object, release decision, or `CURRENT` assurance.
28
+
29
+ Without an agent repository, run `npx witnora@latest try --template workflow`
30
+ for an offline-only sample.
31
+
32
+ To exercise the privacy-preserving high-risk action loop locally, including
33
+ contract compilation, exact-action approval, a single-use grant, controlled
34
+ execution, independent outcome observation, signed evidence, and offline
35
+ verification:
36
+
37
+ ```bash
38
+ npx witnora@latest contract init
39
+ npx witnora@latest contract compile witnora.assurance-contract.json
40
+ npx witnora@latest privacy init
41
+ npx witnora@latest demo assurance-loop
42
+ npx witnora@latest verify --offline .witnora/assurance-loop/witnora-evidence-packet.json \
43
+ --trust .witnora/assurance-loop/witnora-trust-bundle.json
44
+ ```
45
+
46
+ This reference path is deterministic and local. It uses no real integration or
47
+ credential. See the full [privacy-preserving action loop](https://github.com/Kakarottoooo/witnora/blob/main/docs/how-it-works/privacy-preserving-action-loop.md),
48
+ including attack demonstrations and explicit limitations.
49
+
50
+ Implementation references:
51
+
52
+ - [Assurance Contract v0.1](https://github.com/Kakarottoooo/witnora/blob/main/docs/specifications/witnora-assurance-contract-v0.1.md)
53
+ - [Two-hour design-partner quickstart](https://github.com/Kakarottoooo/witnora/blob/main/docs/guides/design-partner-two-hour-quickstart.md)
54
+ - [Threat model and trust boundaries](https://github.com/Kakarottoooo/witnora/blob/main/docs/security/privacy-verifiable-assurance-v1-threat-model.md)
55
+
56
+ If you use the offline sample, inspect `.witnora/try/agentcert-report.html` and
23
57
  `.witnora/try/agentcert-evidence.json`. The output is explicitly synthetic and
24
58
  reported-strength: it proves the evidence tooling works, not that a real agent
25
59
  is reliable.
@@ -92,13 +126,35 @@ Default outputs:
92
126
  - `.witnora/latest/reviewed-failure-dataset.jsonl`
93
127
  - `.witnora/latest/monitor.json`
94
128
 
95
- Push the validated evidence bundle into a hosted Witnora project:
129
+ Onboard once, then push the validated evidence bundle into the same project:
96
130
 
97
131
  ```bash
98
- npx witnora connect --server https://witnora.com --project your-project-id
99
- npx witnora push --evidence .witnora/latest/agentcert-evidence.json
132
+ npx witnora@latest onboard --project your-project-id
133
+ npx witnora@latest push --evidence .witnora/latest/agentcert-evidence.json
100
134
  ```
101
135
 
136
+ ### Automatic agent and environment context
137
+
138
+ `run --push`, `push`, and the generated workflow/coding/data adapter register
139
+ the observed agent automatically. Witnora derives the stable identity and
140
+ version from the command subject, the nearest `package.json`, and the current
141
+ CI commit. It records one canonical environment for every run:
142
+
143
+ - local, development, and test execution becomes `sandbox`;
144
+ - pull-request and preview execution becomes `staging`;
145
+ - tagged releases and recognized production hosts become `production`.
146
+
147
+ This context is run evidence, not a project form the customer must keep in
148
+ sync. A later run with the same Agent ID updates the observed version without
149
+ overwriting reviewed permissions. Explicit `WITNORA_AGENT_ID`,
150
+ `WITNORA_AGENT_VERSION`, and `WITNORA_ENVIRONMENT` values take precedence when
151
+ a repository needs a stable identity that differs from its package metadata.
152
+ Unsupported environment values fail closed instead of being guessed.
153
+
154
+ Useful one-run overrides are `--agent-id`, `--agent-name`, `--agent-version`,
155
+ `--agent-framework`, and `--environment`. Raw prompts, credentials, and tool
156
+ arguments are not needed for this registration.
157
+
102
158
  Bind a release, pull-request, or nightly run to an issued continuous assurance
103
159
  case by declaring the exact reviewed scope:
104
160
 
@@ -172,7 +228,7 @@ the bundled deterministic adapter contract and writes
172
228
  `.witnora/sandbox/sandbox-adapter-conformance.json`. No `@agentcert` scoped
173
229
  package is required.
174
230
 
175
- After `agentcert connect`, certify and upload in one command:
231
+ After `witnora onboard`, certify and upload in one command:
176
232
 
177
233
  ```bash
178
234
  npx witnora sandbox push --adapter ./agentcert.sandbox.mjs
@@ -299,3 +355,14 @@ npm --prefix packages/witnora-cli ci
299
355
  npm --prefix packages/witnora-cli run build
300
356
  node packages/witnora-cli/dist/cli.js run --profile public-demo
301
357
  ```
358
+
359
+ ## Design-partner independent assurance v0.2
360
+
361
+ The sandbox-only reference path separates a write-capable Gateway from a
362
+ customer-operated read-only Probe, binds approval and a single-use grant to one
363
+ declared GitHub pull-request action, reconciles target state after dispatch, and
364
+ produces a packet that TypeScript and Python can verify offline.
365
+
366
+ Start with the [two-hour design-partner quickstart](../../docs/guides/design-partner-two-hour-quickstart.md).
367
+ The included GitHub target is an emulator. It does not claim live GitHub or
368
+ production acceptance, organizational independence, or external review.
@@ -0,0 +1,312 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { canonicalJson, sha256Canonical, WITNORA_CANONICALIZATION_VERSION } from "./canonical.js";
3
+ export const ASSURANCE_CONTRACT_VERSION = "witnora.assurance_contract.v0.1";
4
+ export const COMPILED_CONTRACT_VERSION = "witnora.compiled_assurance_contract.v0.1";
5
+ export const ASSURANCE_COMPILER_VERSION = "witnora.contract_compiler.v0.1";
6
+ export async function readAssuranceContract(path) {
7
+ const raw = await readFile(path, "utf8");
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(raw);
11
+ }
12
+ catch {
13
+ throw new Error("Contract must be JSON (JSON is valid YAML 1.2). Generated contracts use a .json file to avoid parser ambiguity.");
14
+ }
15
+ return validateAssuranceContract(parsed);
16
+ }
17
+ export function validateAssuranceContract(input) {
18
+ const errors = [];
19
+ const root = object(input, "$", errors);
20
+ exactKeys(root, ["kind", "metadata", "subject", "capability", "authority", "preconditions", "effect", "execution", "observation", "privacy", "evidence", "revalidation"], "$", errors);
21
+ if (root.kind !== ASSURANCE_CONTRACT_VERSION)
22
+ errors.push(`$.kind must equal ${ASSURANCE_CONTRACT_VERSION}.`);
23
+ const metadata = object(root.metadata, "$.metadata", errors);
24
+ exactKeys(metadata, ["id", "version", "description"], "$.metadata", errors);
25
+ const subject = object(root.subject, "$.subject", errors);
26
+ exactKeys(subject, ["agentId", "environment"], "$.subject", errors);
27
+ const capability = object(root.capability, "$.capability", errors);
28
+ exactKeys(capability, ["id", "action", "sideEffect", "risk"], "$.capability", errors);
29
+ const authority = object(root.authority, "$.authority", errors);
30
+ exactKeys(authority, ["mandateRef", "policyRef", "approval", "resourceScope"], "$.authority", errors);
31
+ const approval = object(authority.approval, "$.authority.approval", errors);
32
+ exactKeys(approval, ["required"], "$.authority.approval", errors);
33
+ const resourceScope = object(authority.resourceScope, "$.authority.resourceScope", errors);
34
+ exactKeys(resourceScope, ["type", "pattern"], "$.authority.resourceScope", errors);
35
+ const effect = object(root.effect, "$.effect", errors);
36
+ exactKeys(effect, ["eventuallyWithin", "stableFor", "must", "mustNot"], "$.effect", errors, ["stableFor"]);
37
+ const execution = object(root.execution, "$.execution", errors);
38
+ exactKeys(execution, ["atMostOnce", "idempotencyRequired", "retryPolicy"], "$.execution", errors);
39
+ const retry = object(execution.retryPolicy, "$.execution.retryPolicy", errors);
40
+ exactKeys(retry, ["maxAttempts"], "$.execution.retryPolicy", errors);
41
+ const observation = object(root.observation, "$.observation", errors);
42
+ exactKeys(observation, ["mode", "probeRef", "reconcileWithTargetAuditLog"], "$.observation", errors);
43
+ const privacy = object(root.privacy, "$.privacy", errors);
44
+ exactKeys(privacy, ["localOnly", "disclose", "commitOnly"], "$.privacy", errors);
45
+ const evidence = object(root.evidence, "$.evidence", errors);
46
+ exactKeys(evidence, ["require"], "$.evidence", errors);
47
+ const revalidation = object(root.revalidation, "$.revalidation", errors);
48
+ exactKeys(revalidation, ["triggers"], "$.revalidation", errors);
49
+ requiredString(metadata.id, "$.metadata.id", errors);
50
+ positiveInteger(metadata.version, "$.metadata.version", errors);
51
+ requiredString(metadata.description, "$.metadata.description", errors);
52
+ requiredString(subject.agentId, "$.subject.agentId", errors);
53
+ requiredString(subject.environment, "$.subject.environment", errors);
54
+ requiredString(capability.id, "$.capability.id", errors);
55
+ oneOf(capability.action, ["SUBMIT", "PAY", "SEND", "UPDATE"], "$.capability.action", errors);
56
+ bool(capability.sideEffect, "$.capability.sideEffect", errors);
57
+ oneOf(capability.risk, ["low", "medium", "high", "critical"], "$.capability.risk", errors);
58
+ requiredString(authority.mandateRef, "$.authority.mandateRef", errors);
59
+ requiredString(authority.policyRef, "$.authority.policyRef", errors);
60
+ bool(approval.required, "$.authority.approval.required", errors);
61
+ requiredString(resourceScope.type, "$.authority.resourceScope.type", errors);
62
+ requiredString(resourceScope.pattern, "$.authority.resourceScope.pattern", errors);
63
+ const preconditions = predicates(root.preconditions, "$.preconditions", errors);
64
+ requiredString(effect.eventuallyWithin, "$.effect.eventuallyWithin", errors);
65
+ if (typeof effect.eventuallyWithin === "string" && parseContractDurationMs(effect.eventuallyWithin) === undefined)
66
+ errors.push("$.effect.eventuallyWithin must be a positive duration such as 30s, 5m, or 1h.");
67
+ if (effect.stableFor !== undefined) {
68
+ requiredString(effect.stableFor, "$.effect.stableFor", errors);
69
+ if (typeof effect.stableFor === "string" && parseContractDurationMs(effect.stableFor) === undefined)
70
+ errors.push("$.effect.stableFor must be a positive duration such as 5s, 1m, or 1h.");
71
+ }
72
+ const must = predicates(effect.must, "$.effect.must", errors);
73
+ const mustNot = predicates(effect.mustNot, "$.effect.mustNot", errors);
74
+ bool(execution.atMostOnce, "$.execution.atMostOnce", errors);
75
+ bool(execution.idempotencyRequired, "$.execution.idempotencyRequired", errors);
76
+ positiveInteger(retry.maxAttempts, "$.execution.retryPolicy.maxAttempts", errors);
77
+ if (observation.mode !== "independent_read_path")
78
+ errors.push("$.observation.mode must equal independent_read_path.");
79
+ requiredString(observation.probeRef, "$.observation.probeRef", errors);
80
+ bool(observation.reconcileWithTargetAuditLog, "$.observation.reconcileWithTargetAuditLog", errors);
81
+ const localOnly = strings(privacy.localOnly, "$.privacy.localOnly", errors);
82
+ const disclose = strings(privacy.disclose, "$.privacy.disclose", errors);
83
+ const commitOnly = strings(privacy.commitOnly, "$.privacy.commitOnly", errors);
84
+ const overlap = [...localOnly, ...disclose, ...commitOnly].filter((value, index, all) => all.indexOf(value) !== index);
85
+ if (overlap.length)
86
+ errors.push(`$.privacy categories overlap: ${[...new Set(overlap)].join(", ")}.`);
87
+ const requireEvidence = strings(evidence.require, "$.evidence.require", errors);
88
+ const triggers = strings(revalidation.triggers, "$.revalidation.triggers", errors);
89
+ if (capability.sideEffect === true && ["high", "critical"].includes(String(capability.risk)) && approval.required !== true) {
90
+ errors.push("$.authority.approval.required must be true for a high-risk side effect.");
91
+ }
92
+ if (execution.atMostOnce !== true || execution.idempotencyRequired !== true)
93
+ errors.push("High-risk v0.1 contracts require at-most-once grant use and target idempotency.");
94
+ if (!must.length)
95
+ errors.push("$.effect.must must contain at least one independently observable predicate.");
96
+ if (errors.length)
97
+ throw new Error(`Invalid Assurance Contract:\n${errors.map((error) => `- ${error}`).join("\n")}`);
98
+ return structuredClone(input);
99
+ }
100
+ export function compileAssuranceContract(input) {
101
+ const contract = validateAssuranceContract(input);
102
+ const sourceContractDigest = sha256Canonical(contract);
103
+ const artifacts = {
104
+ capabilityRegistration: contract.capability,
105
+ runtimePolicyInput: { ...contract.authority, contractDigest: sourceContractDigest },
106
+ approvalPresentation: { capability: contract.capability.id, action: contract.capability.action, resourceType: contract.authority.resourceScope.type, exactActionDigestRequired: true },
107
+ executionGrantBindingTemplate: {
108
+ contractDigest: sourceContractDigest,
109
+ policyRef: contract.authority.policyRef,
110
+ mandateRef: contract.authority.mandateRef,
111
+ agentId: contract.subject.agentId,
112
+ environment: contract.subject.environment,
113
+ maxUses: 1,
114
+ },
115
+ outcomeProbeSpecification: { ...contract.observation, ...contract.effect },
116
+ evidenceObligations: [...contract.evidence.require].sort(),
117
+ privacyObligations: contract.privacy,
118
+ revalidationFingerprint: sha256Canonical({ triggers: contract.revalidation.triggers, values: revalidationValues(contract) }),
119
+ };
120
+ return {
121
+ schemaVersion: COMPILED_CONTRACT_VERSION,
122
+ compilerVersion: ASSURANCE_COMPILER_VERSION,
123
+ canonicalizationVersion: WITNORA_CANONICALIZATION_VERSION,
124
+ sourceContractDigest,
125
+ ...artifacts,
126
+ artifactDigests: Object.fromEntries(Object.entries(artifacts).map(([key, value]) => [key, sha256Canonical(value)])),
127
+ };
128
+ }
129
+ export function parseContractDurationMs(value) {
130
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)$/.exec(value);
131
+ if (!match)
132
+ return undefined;
133
+ const amount = Number(match[1]);
134
+ const multiplier = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000, d: 86_400_000 }[match[2]];
135
+ if (multiplier === undefined)
136
+ return undefined;
137
+ const result = amount * multiplier;
138
+ return Number.isFinite(result) && result > 0 ? result : undefined;
139
+ }
140
+ export function diffAssuranceContracts(previousInput, nextInput) {
141
+ const previous = validateAssuranceContract(previousInput);
142
+ const next = validateAssuranceContract(nextInput);
143
+ const changedPaths = changed(previous, next);
144
+ const reasons = new Set();
145
+ if (changedPaths.some((path) => path.startsWith("capability")))
146
+ reasons.add("CAPABILITY_CHANGED");
147
+ if (changedPaths.some((path) => path.startsWith("authority.resourceScope")))
148
+ reasons.add("RESOURCE_SCOPE_CHANGED");
149
+ if (changedPaths.some((path) => path.startsWith("authority.approval")))
150
+ reasons.add("APPROVAL_REQUIREMENT_CHANGED");
151
+ if (changedPaths.some((path) => path.startsWith("authority.policyRef")))
152
+ reasons.add("POLICY_VERSION_CHANGED");
153
+ if (changedPaths.some((path) => path.startsWith("effect") || path.startsWith("observation")))
154
+ reasons.add("OUTCOME_SEMANTICS_CHANGED");
155
+ if (changedPaths.some((path) => path.startsWith("privacy")))
156
+ reasons.add("PRIVACY_DISCLOSURE_CHANGED");
157
+ if (changedPaths.some((path) => path.startsWith("subject")))
158
+ reasons.add("SUBJECT_SCOPE_CHANGED");
159
+ const authorityExpanded = scopeExpanded(previous, next);
160
+ if (authorityExpanded)
161
+ reasons.add("AUTHORITY_EXPANDED");
162
+ return {
163
+ schemaVersion: "witnora.assurance_contract_diff.v0.1",
164
+ revalidationRequired: reasons.size > 0,
165
+ authorityExpanded,
166
+ reasons: [...reasons].sort(),
167
+ changedPaths,
168
+ };
169
+ }
170
+ export function exampleAssuranceContract() {
171
+ return {
172
+ kind: ASSURANCE_CONTRACT_VERSION,
173
+ metadata: { id: "record-submit", version: 1, description: "Submit one record through a controlled action path." },
174
+ subject: { agentId: "example-workflow-agent", environment: "sandbox" },
175
+ capability: { id: "record.submit", action: "SUBMIT", sideEffect: true, risk: "high" },
176
+ authority: {
177
+ mandateRef: "mandate:record-submit:v1",
178
+ policyRef: "policy:high-risk-submit:v1",
179
+ approval: { required: true },
180
+ resourceScope: { type: "record", pattern: "sandbox/*" },
181
+ },
182
+ preconditions: [{ path: "status", operator: "equals", value: "DRAFT" }],
183
+ effect: {
184
+ eventuallyWithin: "30s",
185
+ must: [{ path: "status", operator: "equals", value: "SUBMITTED" }],
186
+ mustNot: [{ path: "prohibitedSideEffect", operator: "equals", value: true }],
187
+ },
188
+ execution: { atMostOnce: true, idempotencyRequired: true, retryPolicy: { maxAttempts: 1 } },
189
+ observation: { mode: "independent_read_path", probeRef: "probe:record-status:v1", reconcileWithTargetAuditLog: true },
190
+ privacy: {
191
+ localOnly: ["prompt", "agentSource", "credentials", "rawArguments", "rawResult"],
192
+ disclose: ["actionType", "policyDecision", "outcomeStatus"],
193
+ commitOnly: ["resourceId", "canonicalAction"],
194
+ },
195
+ evidence: { require: ["sourceSignature", "serverReceipt", "grantBinding", "outcomeObservation", "sequenceContinuity"] },
196
+ revalidation: { triggers: ["agentVersion", "modelVersion", "promptDigest", "toolSchemaDigest", "policyVersion", "probeVersion"] },
197
+ };
198
+ }
199
+ export function resourceMatchesScope(scope, resourceId) {
200
+ if (!scope.type || !resourceId)
201
+ return false;
202
+ const wildcard = scope.pattern.indexOf("*");
203
+ if (wildcard < 0)
204
+ return resourceId === scope.pattern;
205
+ if (wildcard !== scope.pattern.length - 1 || scope.pattern.indexOf("*", wildcard + 1) >= 0)
206
+ return false;
207
+ return resourceId.startsWith(scope.pattern.slice(0, -1));
208
+ }
209
+ export function renderContractExplanation(contract) {
210
+ const compiled = compileAssuranceContract(contract);
211
+ return [
212
+ `Contract valid: ${contract.metadata.id} v${contract.metadata.version}`,
213
+ `Contract digest: ${compiled.sourceContractDigest}`,
214
+ `Capability: ${contract.capability.id} (${contract.capability.action}, ${contract.capability.risk} risk)`,
215
+ `Approval: ${contract.authority.approval.required ? "required and bound to exact action digest" : "not required"}`,
216
+ `Outcome probe: ${contract.observation.probeRef} via ${contract.observation.mode}`,
217
+ `Cloud disclosure: ${contract.privacy.disclose.join(", ") || "none"}`,
218
+ `Commitment only: ${contract.privacy.commitOnly.join(", ") || "none"}`,
219
+ `Local only: ${contract.privacy.localOnly.join(", ") || "none"}`,
220
+ `Revalidation triggers: ${contract.revalidation.triggers.join(", ")}`,
221
+ "Enforcement caveat: this contract covers only dispatches mediated by a credential-isolated Gateway.",
222
+ ].join("\n") + "\n";
223
+ }
224
+ function revalidationValues(contract) {
225
+ return {
226
+ agentId: contract.subject.agentId,
227
+ environment: contract.subject.environment,
228
+ capability: contract.capability,
229
+ authority: contract.authority,
230
+ effect: contract.effect,
231
+ observation: contract.observation,
232
+ privacy: contract.privacy,
233
+ };
234
+ }
235
+ function scopeExpanded(previous, next) {
236
+ if (previous.authority.resourceScope.pattern !== next.authority.resourceScope.pattern && next.authority.resourceScope.pattern === "*")
237
+ return true;
238
+ if (previous.authority.approval.required && !next.authority.approval.required)
239
+ return true;
240
+ if (!previous.capability.sideEffect && next.capability.sideEffect)
241
+ return true;
242
+ const rank = { low: 0, medium: 1, high: 2, critical: 3 };
243
+ return rank[next.capability.risk] > rank[previous.capability.risk];
244
+ }
245
+ function changed(previous, next, path = "") {
246
+ if (canonicalJson(previous) === canonicalJson(next))
247
+ return [];
248
+ if (Array.isArray(previous) || Array.isArray(next) || !previous || !next || typeof previous !== "object" || typeof next !== "object")
249
+ return [path || "$"];
250
+ const left = previous;
251
+ const right = next;
252
+ return [...new Set([...Object.keys(left), ...Object.keys(right)])].sort().flatMap((key) => changed(left[key], right[key], path ? `${path}.${key}` : key));
253
+ }
254
+ function predicates(value, path, errors) {
255
+ if (!Array.isArray(value)) {
256
+ errors.push(`${path} must be an array.`);
257
+ return [];
258
+ }
259
+ return value.map((item, index) => {
260
+ const itemPath = `${path}[${index}]`;
261
+ const result = object(item, itemPath, errors);
262
+ exactKeys(result, ["path", "operator", "value"], itemPath, errors, ["value"]);
263
+ requiredString(result.path, `${itemPath}.path`, errors);
264
+ oneOf(result.operator, ["equals", "not_equals", "exists", "not_exists", "contains", "count"], `${itemPath}.operator`, errors);
265
+ if (!["exists", "not_exists"].includes(String(result.operator)) && result.value === undefined)
266
+ errors.push(`${itemPath}.value is required for ${String(result.operator)}.`);
267
+ return result;
268
+ });
269
+ }
270
+ function object(value, path, errors) {
271
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
272
+ errors.push(`${path} must be an object.`);
273
+ return {};
274
+ }
275
+ return value;
276
+ }
277
+ function exactKeys(value, allowed, path, errors, optional = []) {
278
+ for (const key of Object.keys(value))
279
+ if (!allowed.includes(key))
280
+ errors.push(`${path}.${key} is not allowed.`);
281
+ for (const key of allowed)
282
+ if (!optional.includes(key) && !(key in value))
283
+ errors.push(`${path}.${key} is required.`);
284
+ }
285
+ function requiredString(value, path, errors) {
286
+ if (typeof value !== "string" || !value.trim())
287
+ errors.push(`${path} must be a non-empty string.`);
288
+ }
289
+ function strings(value, path, errors) {
290
+ if (!Array.isArray(value)) {
291
+ errors.push(`${path} must be an array.`);
292
+ return [];
293
+ }
294
+ const output = [];
295
+ value.forEach((item, index) => { requiredString(item, `${path}[${index}]`, errors); if (typeof item === "string")
296
+ output.push(item); });
297
+ if (new Set(output).size !== output.length)
298
+ errors.push(`${path} must not contain duplicates.`);
299
+ return output;
300
+ }
301
+ function positiveInteger(value, path, errors) {
302
+ if (!Number.isSafeInteger(value) || Number(value) <= 0)
303
+ errors.push(`${path} must be a positive safe integer.`);
304
+ }
305
+ function bool(value, path, errors) {
306
+ if (typeof value !== "boolean")
307
+ errors.push(`${path} must be Boolean.`);
308
+ }
309
+ function oneOf(value, allowed, path, errors) {
310
+ if (typeof value !== "string" || !allowed.includes(value))
311
+ errors.push(`${path} must be one of ${allowed.join(", ")}.`);
312
+ }