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.
- package/README.md +76 -9
- package/dist/assurance-contract.js +312 -0
- package/dist/assurance-loop-demo.js +245 -0
- package/dist/authorization-state-machine.js +35 -0
- package/dist/authorization-v02.js +60 -0
- package/dist/bundle.js +22 -0
- package/dist/canonical-v02.js +197 -0
- package/dist/canonical.js +31 -0
- package/dist/cli.js +132 -0
- package/dist/command-help.js +81 -0
- package/dist/control-plane.js +12 -3
- package/dist/credentials.js +1 -1
- package/dist/design-partner-pilot.js +402 -0
- package/dist/design-partner-v02.js +449 -0
- package/dist/evidence-v02.js +118 -0
- package/dist/generic-eval.js +160 -0
- package/dist/github-design-partner-v02.js +399 -0
- package/dist/github-live-v02.js +45 -0
- package/dist/guided-setup.js +1 -1
- package/dist/index.js +1 -0
- package/dist/offline-verifier.js +375 -0
- package/dist/onboard.js +132 -0
- package/dist/onboarding-templates.js +49 -6
- package/dist/outcome-evaluator.js +51 -0
- package/dist/privacy-manifest.js +273 -0
- package/dist/probe-protocol-v02.js +101 -0
- package/dist/runtime-context.js +75 -0
- package/dist/sandbox.js +1 -1
- package/dist/schema-validator.js +141 -3
- package/dist/trust-v02.js +111 -0
- package/dist/try.js +1 -1
- package/dist/vendor/design-partner/failure-exercises.mjs +353 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts +8 -0
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.d.ts.map +1 -1
- package/dist/vendor/onegent-runtime/browser-enforcement-runtime.js +43 -1
- package/dist/vendor/witnora-probe/canonical.d.ts +3 -0
- package/dist/vendor/witnora-probe/canonical.js +39 -0
- package/dist/vendor/witnora-probe/cli.d.ts +2 -0
- package/dist/vendor/witnora-probe/cli.js +36 -0
- package/dist/vendor/witnora-probe/config.d.ts +2 -0
- package/dist/vendor/witnora-probe/config.js +63 -0
- package/dist/vendor/witnora-probe/credential-resolver.d.ts +6 -0
- package/dist/vendor/witnora-probe/credential-resolver.js +17 -0
- package/dist/vendor/witnora-probe/crypto.d.ts +2 -0
- package/dist/vendor/witnora-probe/crypto.js +14 -0
- package/dist/vendor/witnora-probe/emulator.d.ts +51 -0
- package/dist/vendor/witnora-probe/emulator.js +164 -0
- package/dist/vendor/witnora-probe/index.d.ts +11 -0
- package/dist/vendor/witnora-probe/index.js +11 -0
- package/dist/vendor/witnora-probe/logger.d.ts +3 -0
- package/dist/vendor/witnora-probe/logger.js +26 -0
- package/dist/vendor/witnora-probe/metrics.d.ts +12 -0
- package/dist/vendor/witnora-probe/metrics.js +31 -0
- package/dist/vendor/witnora-probe/probe.d.ts +22 -0
- package/dist/vendor/witnora-probe/probe.js +208 -0
- package/dist/vendor/witnora-probe/request-verifier.d.ts +9 -0
- package/dist/vendor/witnora-probe/request-verifier.js +84 -0
- package/dist/vendor/witnora-probe/server.d.ts +9 -0
- package/dist/vendor/witnora-probe/server.js +85 -0
- package/dist/vendor/witnora-probe/storage.d.ts +16 -0
- package/dist/vendor/witnora-probe/storage.js +114 -0
- package/dist/vendor/witnora-probe/types.d.ts +126 -0
- package/dist/vendor/witnora-probe/types.js +2 -0
- package/dist/vendor/witnora-verifier-python/README.md +43 -0
- package/dist/vendor/witnora-verifier-python/pyproject.toml +23 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/__init__.py +33 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/canonical.py +218 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/cli.py +75 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/conformance.py +124 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/crypto.py +139 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/errors.py +15 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/event_chain.py +80 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/evidence.py +336 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/probe.py +188 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/producer.py +127 -0
- package/dist/vendor/witnora-verifier-python/src/witnora_verifier/trust.py +337 -0
- 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/
|
|
12
|
+
[GitHub source](https://github.com/Kakarottoooo/witnora)
|
|
13
13
|
|
|
14
|
-
## 5-minute
|
|
14
|
+
## 5-minute hosted path
|
|
15
15
|
|
|
16
|
-
|
|
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
|
|
20
|
+
npx witnora@latest onboard --project your-project-id
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
|
|
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
|
-
|
|
129
|
+
Onboard once, then push the validated evidence bundle into the same project:
|
|
96
130
|
|
|
97
131
|
```bash
|
|
98
|
-
npx witnora
|
|
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 `
|
|
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
|
+
}
|