witnora 0.9.3 → 0.10.0
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 +22 -0
- package/dist/bundle.js +22 -0
- package/dist/cli.js +14 -0
- package/dist/command-help.js +5 -0
- package/dist/control-plane.js +9 -0
- package/dist/onboarding-templates.js +46 -3
- package/dist/runtime-context.js +75 -0
- package/dist/schema-validator.js +141 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -99,6 +99,28 @@ npx witnora connect --server https://witnora.com --project your-project-id
|
|
|
99
99
|
npx witnora push --evidence .witnora/latest/agentcert-evidence.json
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
### Automatic agent and environment context
|
|
103
|
+
|
|
104
|
+
`run --push`, `push`, and the generated workflow/coding/data adapter register
|
|
105
|
+
the observed agent automatically. Witnora derives the stable identity and
|
|
106
|
+
version from the command subject, the nearest `package.json`, and the current
|
|
107
|
+
CI commit. It records one canonical environment for every run:
|
|
108
|
+
|
|
109
|
+
- local, development, and test execution becomes `sandbox`;
|
|
110
|
+
- pull-request and preview execution becomes `staging`;
|
|
111
|
+
- tagged releases and recognized production hosts become `production`.
|
|
112
|
+
|
|
113
|
+
This context is run evidence, not a project form the customer must keep in
|
|
114
|
+
sync. A later run with the same Agent ID updates the observed version without
|
|
115
|
+
overwriting reviewed permissions. Explicit `WITNORA_AGENT_ID`,
|
|
116
|
+
`WITNORA_AGENT_VERSION`, and `WITNORA_ENVIRONMENT` values take precedence when
|
|
117
|
+
a repository needs a stable identity that differs from its package metadata.
|
|
118
|
+
Unsupported environment values fail closed instead of being guessed.
|
|
119
|
+
|
|
120
|
+
Useful one-run overrides are `--agent-id`, `--agent-name`, `--agent-version`,
|
|
121
|
+
`--agent-framework`, and `--environment`. Raw prompts, credentials, and tool
|
|
122
|
+
arguments are not needed for this registration.
|
|
123
|
+
|
|
102
124
|
Bind a release, pull-request, or nightly run to an issued continuous assurance
|
|
103
125
|
case by declaring the exact reviewed scope:
|
|
104
126
|
|
package/dist/bundle.js
CHANGED
|
@@ -75,8 +75,30 @@ function evidenceStrengthFor(results) {
|
|
|
75
75
|
level: weakest.level,
|
|
76
76
|
claims: [...new Set(values.flatMap((value) => value.claims))],
|
|
77
77
|
limitations: [...new Set(values.flatMap((value) => value.limitations))],
|
|
78
|
+
...(() => {
|
|
79
|
+
const vectors = values.flatMap((value) => value.trustVector ? [value.trustVector] : []);
|
|
80
|
+
return vectors.length === values.length && vectors.length ? { trustVector: weakestTrustVector(vectors) } : {};
|
|
81
|
+
})(),
|
|
78
82
|
};
|
|
79
83
|
}
|
|
84
|
+
function weakestTrustVector(vectors) {
|
|
85
|
+
const capture = weakest(vectors.map((item) => item.capture), ["self_reported", "collector_recorded", "boundary_observed"]);
|
|
86
|
+
const mediation = weakest(vectors.map((item) => item.mediation), ["none", "advisory", "enforced"]);
|
|
87
|
+
const completeness = weakest(vectors.map((item) => item.completeness), ["unknown", "partial", "target_reconciled"]);
|
|
88
|
+
const attestation = weakest(vectors.map((item) => item.attestation), ["unsigned", "source_signed", "platform_signed", "externally_witnessed"]);
|
|
89
|
+
const review = weakest(vectors.map((item) => item.review), ["none", "internal", "independent_external"]);
|
|
90
|
+
const outcome = vectors.some((item) => item.outcome === "contradicted")
|
|
91
|
+
? "contradicted"
|
|
92
|
+
: vectors.some((item) => item.outcome === "unverified") ? "unverified" : "verified";
|
|
93
|
+
return {
|
|
94
|
+
schemaVersion: "witnora.evidence_trust_vector.v0.1",
|
|
95
|
+
capture, mediation, outcome, completeness, attestation, review,
|
|
96
|
+
limitations: [...new Set(vectors.flatMap((item) => item.limitations))],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function weakest(values, order) {
|
|
100
|
+
return values.reduce((left, right) => order.indexOf(left) <= order.indexOf(right) ? left : right);
|
|
101
|
+
}
|
|
80
102
|
function levelForScore(score, passed) {
|
|
81
103
|
if (!passed) {
|
|
82
104
|
return "No assurance decision";
|
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ import { runBrowserAdapterCommand } from "./browser-adapter.js";
|
|
|
28
28
|
import { parseAgentTemplate, starterAdapter, starterGitHubActionWorkflow, starterInstructions, starterProfile, starterTripwireConfig, } from "./onboarding-templates.js";
|
|
29
29
|
import { renderTrySummary, writeTryEvidence } from "./try.js";
|
|
30
30
|
import { applyGuidedSetup } from "./guided-setup.js";
|
|
31
|
+
import { resolveRuntimeContext } from "./runtime-context.js";
|
|
31
32
|
process.on("uncaughtException", reportFatalError);
|
|
32
33
|
process.on("unhandledRejection", reportFatalError);
|
|
33
34
|
const command = process.argv[2] ?? "help";
|
|
@@ -535,6 +536,8 @@ else if (command === "schema") {
|
|
|
535
536
|
witnora schema validate --schema assurance-report --file assurance-report.json
|
|
536
537
|
witnora schema validate --schema assurance-delivery --file assurance-delivery.json
|
|
537
538
|
witnora schema validate --schema evidence-signature --file .witnora/latest/agentcert-evidence.json.sig.json
|
|
539
|
+
witnora schema validate --schema assurance-wallet --file assurance-wallet.json
|
|
540
|
+
witnora schema validate --schema payment-token-reference --file payment-token-reference.json
|
|
538
541
|
`);
|
|
539
542
|
}
|
|
540
543
|
}
|
|
@@ -665,6 +668,17 @@ async function pushHostedEvidence(bundle, bytes, fileName) {
|
|
|
665
668
|
skippedCompanionArtifacts: companions?.skipped,
|
|
666
669
|
assurance,
|
|
667
670
|
verifyContinuousAssurance: Boolean(assurance && (healthOut || requireCurrent)),
|
|
671
|
+
runtimeContext: await resolveRuntimeContext({
|
|
672
|
+
subject: bundle.subject.name,
|
|
673
|
+
framework: readFlag("--agent-framework"),
|
|
674
|
+
env: {
|
|
675
|
+
...process.env,
|
|
676
|
+
...(readFlag("--agent-id") ? { WITNORA_AGENT_ID: readFlag("--agent-id") } : {}),
|
|
677
|
+
...(readFlag("--agent-name") ? { WITNORA_AGENT_NAME: readFlag("--agent-name") } : {}),
|
|
678
|
+
...(readFlag("--agent-version") ? { WITNORA_AGENT_VERSION: readFlag("--agent-version") } : {}),
|
|
679
|
+
...(readFlag("--environment") ? { WITNORA_ENVIRONMENT: readFlag("--environment") } : {}),
|
|
680
|
+
},
|
|
681
|
+
}),
|
|
668
682
|
});
|
|
669
683
|
process.stdout.write(`Hosted run: ${result.runId}\nHosted evidence: ${result.evidenceId}\n`);
|
|
670
684
|
if (companions) {
|
package/dist/command-help.js
CHANGED
|
@@ -59,6 +59,11 @@ Options:
|
|
|
59
59
|
--project <id> Hosted project ID
|
|
60
60
|
--api-key <key> Project API key (prefer WITNORA_API_KEY in CI)
|
|
61
61
|
--external-id <id> Idempotent hosted run ID
|
|
62
|
+
--agent-id <id> Stable observed Agent identity (default: evidence subject)
|
|
63
|
+
--agent-name <name> Human-readable observed Agent name
|
|
64
|
+
--agent-version <v> Version override (default: package or CI commit)
|
|
65
|
+
--agent-framework <f> Framework metadata override
|
|
66
|
+
--environment <env> sandbox, staging, or production
|
|
62
67
|
--assurance-case <id> Issued assurance case to reconcile
|
|
63
68
|
--assurance-scope <p> Declared agent/model/prompt/tools/policy/suite scope JSON
|
|
64
69
|
--assurance-trigger <t> auto, pull_request, release, or nightly (default: auto)
|
package/dist/control-plane.js
CHANGED
|
@@ -75,6 +75,15 @@ export async function pushEvidenceToControlPlane(options) {
|
|
|
75
75
|
subject: bundle.subject,
|
|
76
76
|
products: bundle.summary.products,
|
|
77
77
|
},
|
|
78
|
+
...(options.runtimeContext ? {
|
|
79
|
+
agent: {
|
|
80
|
+
externalId: options.runtimeContext.agentId,
|
|
81
|
+
name: options.runtimeContext.agentName,
|
|
82
|
+
version: options.runtimeContext.agentVersion,
|
|
83
|
+
framework: options.runtimeContext.framework,
|
|
84
|
+
},
|
|
85
|
+
environment: options.runtimeContext.environment,
|
|
86
|
+
} : {}),
|
|
78
87
|
assurance: options.assurance,
|
|
79
88
|
}),
|
|
80
89
|
});
|
|
@@ -32,10 +32,15 @@ export function starterAdapter(template, subject) {
|
|
|
32
32
|
const eventType = template === "coding" ? "coding.change.proposed" : template === "workflow" ? "workflow.step.completed" : "data.query.completed";
|
|
33
33
|
return `#!/usr/bin/env node
|
|
34
34
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
35
|
+
import { readFile } from "node:fs/promises";
|
|
35
36
|
|
|
36
37
|
const baseUrl = brandedRequired("BASE_URL").replace(/\\\/$/, "");
|
|
37
38
|
const projectId = brandedRequired("PROJECT_ID");
|
|
38
39
|
const apiKey = brandedRequired("API_KEY");
|
|
40
|
+
const agentId = branded("AGENT_ID") ?? ${JSON.stringify(subject)};
|
|
41
|
+
const agentName = branded("AGENT_NAME") ?? humanize(agentId);
|
|
42
|
+
const agentVersion = await discoverAgentVersion();
|
|
43
|
+
const environment = detectEnvironment();
|
|
39
44
|
const now = new Date().toISOString();
|
|
40
45
|
const traceId = randomBytes(16).toString("hex");
|
|
41
46
|
const spanId = randomBytes(8).toString("hex");
|
|
@@ -44,10 +49,10 @@ const envelope = {
|
|
|
44
49
|
envelopeId: randomUUID(),
|
|
45
50
|
kind: "event",
|
|
46
51
|
occurredAt: now,
|
|
47
|
-
source: { agentId
|
|
48
|
-
run: { externalId: process.env.AGENT_RUN_ID ?? randomUUID(), kind: "custom" },
|
|
52
|
+
source: { agentId, agentName, agentVersion, framework: branded("AGENT_FRAMEWORK") ?? ${JSON.stringify(framework)}, adapter: "witnora-init-v0.3" },
|
|
53
|
+
run: { externalId: process.env.WITNORA_RUN_ID ?? process.env.AGENT_RUN_ID ?? randomUUID(), kind: "custom", environment: environment },
|
|
49
54
|
trace: { traceId, spanId },
|
|
50
|
-
event: { type: ${JSON.stringify(eventType)}, actor: "agent", sequence: 0, attributes: { environment
|
|
55
|
+
event: { type: ${JSON.stringify(eventType)}, actor: "agent", sequence: 0, attributes: { environment } },
|
|
51
56
|
};
|
|
52
57
|
const response = await fetch(\`\${baseUrl}/v1/projects/\${encodeURIComponent(projectId)}/envelopes\`, {
|
|
53
58
|
method: "POST",
|
|
@@ -66,6 +71,44 @@ function brandedRequired(suffix) {
|
|
|
66
71
|
if (!value) throw new Error(\`\${name} is required. Connect the Witnora CLI or set the hosted project variables. Legacy AGENTCERT_* names remain supported.\`);
|
|
67
72
|
return value;
|
|
68
73
|
}
|
|
74
|
+
|
|
75
|
+
function branded(suffix) {
|
|
76
|
+
return process.env[\`WITNORA_\${suffix}\`] ?? process.env[\`AGENTCERT_\${suffix}\`];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function humanize(value) {
|
|
80
|
+
const unscoped = value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : value;
|
|
81
|
+
return unscoped.replace(/[-_.]+/g, " ").replace(/\\b\\w/g, (character) => character.toUpperCase()).trim();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function discoverAgentVersion() {
|
|
85
|
+
const explicit = branded("AGENT_VERSION");
|
|
86
|
+
if (explicit) return explicit;
|
|
87
|
+
try {
|
|
88
|
+
const pkg = JSON.parse(await readFile(new URL("./package.json", import.meta.url), "utf8"));
|
|
89
|
+
if (typeof pkg.version === "string" && pkg.version.trim()) return pkg.version.trim();
|
|
90
|
+
} catch {}
|
|
91
|
+
const commit = process.env.GITHUB_SHA ?? process.env.RENDER_GIT_COMMIT ?? process.env.VERCEL_GIT_COMMIT_SHA;
|
|
92
|
+
return commit ? \`git-\${commit.slice(0, 12)}\` : "local";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function detectEnvironment() {
|
|
96
|
+
const explicit = branded("ENVIRONMENT") ?? process.env.VERCEL_ENV;
|
|
97
|
+
if (explicit) return normalizeEnvironment(explicit);
|
|
98
|
+
if (["pull_request", "pull_request_target"].includes(process.env.GITHUB_EVENT_NAME ?? "")) return "staging";
|
|
99
|
+
if (process.env.GITHUB_REF_TYPE === "tag") return "production";
|
|
100
|
+
if (process.env.RENDER_SERVICE_ID) return process.env.IS_PULL_REQUEST === "true" ? "staging" : "production";
|
|
101
|
+
if (process.env.NODE_ENV === "production") return "production";
|
|
102
|
+
return "sandbox";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeEnvironment(value) {
|
|
106
|
+
const normalized = value.trim().toLowerCase();
|
|
107
|
+
if (["sandbox", "development", "dev", "test", "local"].includes(normalized)) return "sandbox";
|
|
108
|
+
if (["staging", "stage", "preview", "pull_request", "pr"].includes(normalized)) return "staging";
|
|
109
|
+
if (["production", "prod", "live"].includes(normalized)) return "production";
|
|
110
|
+
throw new Error(\`Unsupported Witnora environment \${JSON.stringify(value)}. Use sandbox, staging, or production.\`);
|
|
111
|
+
}
|
|
69
112
|
`;
|
|
70
113
|
}
|
|
71
114
|
export function starterInstructions(template, subject) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
export async function resolveRuntimeContext(options) {
|
|
4
|
+
const env = options.env ?? process.env;
|
|
5
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
6
|
+
const packageMetadata = await nearestPackageMetadata(cwd);
|
|
7
|
+
const agentId = first(env.WITNORA_AGENT_ID, env.AGENTCERT_AGENT_ID, options.subject, packageMetadata?.name) ?? "witnora-agent";
|
|
8
|
+
const agentName = first(env.WITNORA_AGENT_NAME, env.AGENTCERT_AGENT_NAME) ?? humanizeIdentifier(agentId);
|
|
9
|
+
return {
|
|
10
|
+
agentId,
|
|
11
|
+
agentName,
|
|
12
|
+
agentVersion: first(env.WITNORA_AGENT_VERSION, env.AGENTCERT_AGENT_VERSION, packageMetadata?.version, commitVersion(env), "local"),
|
|
13
|
+
...(first(env.WITNORA_AGENT_FRAMEWORK, env.AGENTCERT_AGENT_FRAMEWORK, options.framework)
|
|
14
|
+
? { framework: first(env.WITNORA_AGENT_FRAMEWORK, env.AGENTCERT_AGENT_FRAMEWORK, options.framework) }
|
|
15
|
+
: {}),
|
|
16
|
+
environment: detectRuntimeEnvironment(env),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function humanizeIdentifier(value) {
|
|
20
|
+
const unscoped = value.includes("/") ? value.slice(value.lastIndexOf("/") + 1) : value;
|
|
21
|
+
return unscoped.replace(/[-_.]+/g, " ").replace(/\b\w/g, (character) => character.toUpperCase()).trim();
|
|
22
|
+
}
|
|
23
|
+
export function detectRuntimeEnvironment(env) {
|
|
24
|
+
const explicit = first(env.WITNORA_ENVIRONMENT, env.AGENTCERT_ENVIRONMENT);
|
|
25
|
+
if (explicit)
|
|
26
|
+
return normalizeRuntimeEnvironment(explicit);
|
|
27
|
+
if (env.VERCEL_ENV)
|
|
28
|
+
return normalizeRuntimeEnvironment(env.VERCEL_ENV);
|
|
29
|
+
if (env.GITHUB_EVENT_NAME === "pull_request" || env.GITHUB_EVENT_NAME === "pull_request_target")
|
|
30
|
+
return "staging";
|
|
31
|
+
if (env.GITHUB_REF_TYPE === "tag")
|
|
32
|
+
return "production";
|
|
33
|
+
if (env.RENDER_SERVICE_ID)
|
|
34
|
+
return env.IS_PULL_REQUEST === "true" ? "staging" : "production";
|
|
35
|
+
if (env.NODE_ENV === "production")
|
|
36
|
+
return "production";
|
|
37
|
+
return "sandbox";
|
|
38
|
+
}
|
|
39
|
+
export function normalizeRuntimeEnvironment(value) {
|
|
40
|
+
const normalized = value.trim().toLowerCase();
|
|
41
|
+
if (["sandbox", "development", "dev", "test", "local"].includes(normalized))
|
|
42
|
+
return "sandbox";
|
|
43
|
+
if (["staging", "stage", "preview", "pull_request", "pr"].includes(normalized))
|
|
44
|
+
return "staging";
|
|
45
|
+
if (["production", "prod", "live"].includes(normalized))
|
|
46
|
+
return "production";
|
|
47
|
+
throw new Error(`Unsupported Witnora environment ${JSON.stringify(value)}. Use sandbox, staging, or production.`);
|
|
48
|
+
}
|
|
49
|
+
async function nearestPackageMetadata(start) {
|
|
50
|
+
let current = start;
|
|
51
|
+
while (true) {
|
|
52
|
+
try {
|
|
53
|
+
const value = JSON.parse(await readFile(join(current, "package.json"), "utf8"));
|
|
54
|
+
return { name: text(value.name), version: text(value.version) };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (error.code !== "ENOENT" && !(error instanceof SyntaxError))
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
const parent = dirname(current);
|
|
61
|
+
if (parent === current)
|
|
62
|
+
return undefined;
|
|
63
|
+
current = parent;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function commitVersion(env) {
|
|
67
|
+
const sha = first(env.GITHUB_SHA, env.RENDER_GIT_COMMIT, env.VERCEL_GIT_COMMIT_SHA);
|
|
68
|
+
return sha ? `git-${sha.slice(0, 12)}` : undefined;
|
|
69
|
+
}
|
|
70
|
+
function first(...values) {
|
|
71
|
+
return values.find((value) => typeof value === "string" && value.trim().length > 0)?.trim();
|
|
72
|
+
}
|
|
73
|
+
function text(value) {
|
|
74
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
75
|
+
}
|
package/dist/schema-validator.js
CHANGED
|
@@ -23,10 +23,12 @@ export function parseSchemaId(input) {
|
|
|
23
23
|
value === "evolution-manifest" ||
|
|
24
24
|
value === "permission-diff" ||
|
|
25
25
|
value === "promotion-grant" ||
|
|
26
|
-
value === "promotion-receipt"
|
|
26
|
+
value === "promotion-receipt" ||
|
|
27
|
+
value === "assurance-wallet" ||
|
|
28
|
+
value === "payment-token-reference") {
|
|
27
29
|
return value;
|
|
28
30
|
}
|
|
29
|
-
throw new Error(`Unsupported schema "${value}". Use evidence-bundle, result, corpus-record, failure-review, classifier-eval, monitor-snapshot, robustness-lab, release-gate, assurance-report, assurance-delivery, assurance-scope, evidence-signature, evidence-strength, action-mandate, trusted-action-record, trusted-run-receipt, agent-mutation, evolution-manifest, permission-diff, promotion-grant, or
|
|
31
|
+
throw new Error(`Unsupported schema "${value}". Use evidence-bundle, result, corpus-record, failure-review, classifier-eval, monitor-snapshot, robustness-lab, release-gate, assurance-report, assurance-delivery, assurance-scope, evidence-signature, evidence-strength, action-mandate, trusted-action-record, trusted-run-receipt, agent-mutation, evolution-manifest, permission-diff, promotion-grant, promotion-receipt, assurance-wallet, or payment-token-reference.`);
|
|
30
32
|
}
|
|
31
33
|
export function validateAgentCertSchema(schema, input) {
|
|
32
34
|
const errors = [];
|
|
@@ -78,9 +80,100 @@ export function validateAgentCertSchema(schema, input) {
|
|
|
78
80
|
validatePromotionGrant(value, errors);
|
|
79
81
|
if (schema === "promotion-receipt")
|
|
80
82
|
validatePromotionReceipt(value, errors);
|
|
83
|
+
if (schema === "assurance-wallet")
|
|
84
|
+
validateAssuranceWallet(value, errors);
|
|
85
|
+
if (schema === "payment-token-reference")
|
|
86
|
+
validatePaymentTokenReference(value, errors);
|
|
81
87
|
}
|
|
82
88
|
return { schema, valid: errors.length === 0, errors };
|
|
83
89
|
}
|
|
90
|
+
function validateAssuranceWallet(value, errors) {
|
|
91
|
+
requiredConst(value, "schemaVersion", "witnora.assurance_wallet.v0.1", errors);
|
|
92
|
+
for (const field of ["id", "projectId", "name", "ownerPrincipalId", "baseCurrency", "policyDigestSha256", "status", "createdBy", "createdAt", "updatedAt"])
|
|
93
|
+
requiredString(value, field, errors);
|
|
94
|
+
sha256Field(value, "policyDigestSha256", errors);
|
|
95
|
+
requiredEnum(value, "status", ["ACTIVE", "SUSPENDED", "REVOKED"], errors);
|
|
96
|
+
for (const field of ["createdAt", "updatedAt"])
|
|
97
|
+
validateTimestamp(value[field], field, errors);
|
|
98
|
+
const policy = recordValue(value.policy);
|
|
99
|
+
if (!policy) {
|
|
100
|
+
errors.push("policy must be an object.");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
requiredArray(policy, "allowedProviders", errors);
|
|
104
|
+
requiredArray(policy, "allowedEnvironments", errors);
|
|
105
|
+
requiredArray(policy, "allowedCurrencies", errors);
|
|
106
|
+
stringArray(policy.allowedProviders, "policy.allowedProviders", errors);
|
|
107
|
+
stringArray(policy.allowedEnvironments, "policy.allowedEnvironments", errors);
|
|
108
|
+
stringArray(policy.allowedCurrencies, "policy.allowedCurrencies", errors);
|
|
109
|
+
enumArray(policy.allowedProviders, "policy.allowedProviders", ["STRIPE", "CLOUDFLARE"], errors);
|
|
110
|
+
enumArray(policy.allowedEnvironments, "policy.allowedEnvironments", ["SANDBOX", "TEST"], errors);
|
|
111
|
+
currencyArray(policy.allowedCurrencies, "policy.allowedCurrencies", errors);
|
|
112
|
+
currencyField(value, "baseCurrency", errors);
|
|
113
|
+
if (Array.isArray(policy.allowedCurrencies) &&
|
|
114
|
+
(policy.allowedCurrencies.length !== 1 || policy.allowedCurrencies[0] !== value.baseCurrency)) {
|
|
115
|
+
errors.push("policy.allowedCurrencies must contain only baseCurrency in v0.1.");
|
|
116
|
+
}
|
|
117
|
+
requiredNumber(policy, "maxAmountPerAction", errors);
|
|
118
|
+
requiredNumber(policy, "maxAmountPerPeriod", errors);
|
|
119
|
+
requiredNumber(policy, "periodSeconds", errors);
|
|
120
|
+
positiveNumber(policy, "maxAmountPerAction", errors);
|
|
121
|
+
positiveNumber(policy, "maxAmountPerPeriod", errors);
|
|
122
|
+
positiveInteger(policy, "periodSeconds", errors);
|
|
123
|
+
requiredBoolean(policy, "mandateRequired", errors);
|
|
124
|
+
requiredBoolean(policy, "independentOutcomeRequired", errors);
|
|
125
|
+
if (policy.mandateRequired !== true)
|
|
126
|
+
errors.push("policy.mandateRequired must be true.");
|
|
127
|
+
if (policy.independentOutcomeRequired !== true)
|
|
128
|
+
errors.push("policy.independentOutcomeRequired must be true.");
|
|
129
|
+
requiredEnum(policy, "recurringPayments", ["DENY", "REQUIRE_APPROVAL"], errors);
|
|
130
|
+
}
|
|
131
|
+
function validatePaymentTokenReference(value, errors) {
|
|
132
|
+
requiredConst(value, "schemaVersion", "witnora.payment_token_reference.v0.1", errors);
|
|
133
|
+
for (const field of ["id", "projectId", "walletId", "providerAccountId", "tokenType", "tokenSha256", "audience", "currency", "expiresAt", "status", "createdBy", "createdAt", "updatedAt"])
|
|
134
|
+
requiredString(value, field, errors);
|
|
135
|
+
sha256Field(value, "tokenSha256", errors);
|
|
136
|
+
requiredNumber(value, "maxAmount", errors);
|
|
137
|
+
positiveNumber(value, "maxAmount", errors);
|
|
138
|
+
currencyField(value, "currency", errors);
|
|
139
|
+
requiredEnum(value, "provider", ["STRIPE", "CLOUDFLARE"], errors);
|
|
140
|
+
requiredEnum(value, "environment", ["SANDBOX", "TEST"], errors);
|
|
141
|
+
requiredEnum(value, "status", ["AVAILABLE", "BOUND", "CONSUMED", "EXPIRED", "REVOKED"], errors);
|
|
142
|
+
for (const field of ["expiresAt", "createdAt", "updatedAt"])
|
|
143
|
+
validateTimestamp(value[field], field, errors);
|
|
144
|
+
}
|
|
145
|
+
function enumArray(value, path, allowed, errors) {
|
|
146
|
+
if (!Array.isArray(value))
|
|
147
|
+
return;
|
|
148
|
+
for (const [index, item] of value.entries()) {
|
|
149
|
+
if (typeof item === "string" && !allowed.includes(item)) {
|
|
150
|
+
errors.push(`${path}[${index}] must be one of: ${allowed.join(", ")}.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function currencyArray(value, path, errors) {
|
|
155
|
+
if (!Array.isArray(value))
|
|
156
|
+
return;
|
|
157
|
+
for (const [index, item] of value.entries()) {
|
|
158
|
+
if (typeof item === "string" && !/^[A-Z]{3}$/.test(item)) {
|
|
159
|
+
errors.push(`${path}[${index}] must be a three-letter uppercase currency code.`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function currencyField(input, key, errors) {
|
|
164
|
+
if (typeof input[key] === "string" && !/^[A-Z]{3}$/.test(input[key])) {
|
|
165
|
+
errors.push(`${key} must be a three-letter uppercase currency code.`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function positiveNumber(input, key, errors) {
|
|
169
|
+
if (typeof input[key] === "number" && input[key] <= 0)
|
|
170
|
+
errors.push(`${key} must be greater than zero.`);
|
|
171
|
+
}
|
|
172
|
+
function positiveInteger(input, key, errors) {
|
|
173
|
+
if (typeof input[key] === "number" && (!Number.isInteger(input[key]) || input[key] <= 0)) {
|
|
174
|
+
errors.push(`${key} must be a positive integer.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
84
177
|
function validateEvidenceBundle(value, errors) {
|
|
85
178
|
requiredConst(value, "schemaName", "agentcert.evidence_bundle", errors);
|
|
86
179
|
requiredConst(value, "schemaVersion", AGENTCERT_EVIDENCE_SCHEMA_VERSION, errors);
|
|
@@ -135,6 +228,21 @@ function validateEvidenceStrength(value, errors, prefix = "") {
|
|
|
135
228
|
requiredArray(value, "limitations", errors);
|
|
136
229
|
stringArray(value.claims, `${prefix}claims`, errors);
|
|
137
230
|
stringArray(value.limitations, `${prefix}limitations`, errors);
|
|
231
|
+
const trust = recordValue(value.trustVector);
|
|
232
|
+
if (value.trustVector !== undefined && !trust)
|
|
233
|
+
errors.push(`${prefix}trustVector must be an object.`);
|
|
234
|
+
if (trust)
|
|
235
|
+
validateEvidenceTrustVector(trust, errors, `${prefix}trustVector.`);
|
|
236
|
+
}
|
|
237
|
+
function validateEvidenceTrustVector(value, errors, prefix) {
|
|
238
|
+
requiredConst(value, "schemaVersion", "witnora.evidence_trust_vector.v0.1", errors);
|
|
239
|
+
requiredEnumAt(value, "capture", ["self_reported", "collector_recorded", "boundary_observed"], `${prefix}capture`, errors);
|
|
240
|
+
requiredEnumAt(value, "mediation", ["none", "advisory", "enforced"], `${prefix}mediation`, errors);
|
|
241
|
+
requiredEnumAt(value, "outcome", ["unverified", "verified", "contradicted"], `${prefix}outcome`, errors);
|
|
242
|
+
requiredEnumAt(value, "completeness", ["unknown", "partial", "target_reconciled"], `${prefix}completeness`, errors);
|
|
243
|
+
requiredEnumAt(value, "attestation", ["unsigned", "source_signed", "platform_signed", "externally_witnessed"], `${prefix}attestation`, errors);
|
|
244
|
+
requiredEnumAt(value, "review", ["none", "internal", "independent_external"], `${prefix}review`, errors);
|
|
245
|
+
stringArray(value.limitations, `${prefix}limitations`, errors);
|
|
138
246
|
}
|
|
139
247
|
function validateActionMandate(value, errors) {
|
|
140
248
|
requiredConst(value, "schemaVersion", "agentcert.action_mandate.v0.1", errors);
|
|
@@ -363,7 +471,9 @@ function validateAssuranceContinuity(input, errors) {
|
|
|
363
471
|
}
|
|
364
472
|
}
|
|
365
473
|
function validateAssuranceScope(value, errors) {
|
|
366
|
-
|
|
474
|
+
if (value.schemaVersion !== "agentcert.assurance_scope.v0.1" && value.schemaVersion !== "agentcert.assurance_scope.v0.2") {
|
|
475
|
+
errors.push("schemaVersion must be agentcert.assurance_scope.v0.1 or agentcert.assurance_scope.v0.2.");
|
|
476
|
+
}
|
|
367
477
|
for (const key of ["agent", "model", "prompt", "tools", "policy", "scenarioSuite"])
|
|
368
478
|
requiredObject(value, key, errors);
|
|
369
479
|
const agent = recordValue(value.agent);
|
|
@@ -394,6 +504,34 @@ function validateAssuranceScope(value, errors) {
|
|
|
394
504
|
requiredStringAt(suite, "version", "scenarioSuite.version", errors);
|
|
395
505
|
requiredSha256At(suite, "sha256", "scenarioSuite.sha256", errors);
|
|
396
506
|
}
|
|
507
|
+
if (value.schemaVersion === "agentcert.assurance_scope.v0.2") {
|
|
508
|
+
for (const key of ["runtimeBoundary", "outcomeProbe", "evidenceTrust"])
|
|
509
|
+
requiredObject(value, key, errors);
|
|
510
|
+
const runtime = recordValue(value.runtimeBoundary);
|
|
511
|
+
const probe = recordValue(value.outcomeProbe);
|
|
512
|
+
const trust = recordValue(value.evidenceTrust);
|
|
513
|
+
if (runtime) {
|
|
514
|
+
requiredSha256At(runtime, "gatewayDigestSha256", "runtimeBoundary.gatewayDigestSha256", errors);
|
|
515
|
+
requiredSha256At(runtime, "adapterDigestSha256", "runtimeBoundary.adapterDigestSha256", errors);
|
|
516
|
+
requiredStringAt(runtime, "credentialBoundary", "runtimeBoundary.credentialBoundary", errors);
|
|
517
|
+
requiredSha256At(runtime, "networkBoundarySha256", "runtimeBoundary.networkBoundarySha256", errors);
|
|
518
|
+
requiredStringAt(runtime, "grantIssuer", "runtimeBoundary.grantIssuer", errors);
|
|
519
|
+
}
|
|
520
|
+
if (probe) {
|
|
521
|
+
requiredStringAt(probe, "profileId", "outcomeProbe.profileId", errors);
|
|
522
|
+
requiredStringAt(probe, "version", "outcomeProbe.version", errors);
|
|
523
|
+
requiredSha256At(probe, "digestSha256", "outcomeProbe.digestSha256", errors);
|
|
524
|
+
requiredStringAt(probe, "authoritativeSource", "outcomeProbe.authoritativeSource", errors);
|
|
525
|
+
requiredStringAt(probe, "readCredentialBoundary", "outcomeProbe.readCredentialBoundary", errors);
|
|
526
|
+
}
|
|
527
|
+
if (trust) {
|
|
528
|
+
requiredSha256At(trust, "collectorDigestSha256", "evidenceTrust.collectorDigestSha256", errors);
|
|
529
|
+
requiredStringAt(trust, "evidenceSchemaVersion", "evidenceTrust.evidenceSchemaVersion", errors);
|
|
530
|
+
requiredStringAt(trust, "signingKeyId", "evidenceTrust.signingKeyId", errors);
|
|
531
|
+
requiredSha256At(trust, "verifierDigestSha256", "evidenceTrust.verifierDigestSha256", errors);
|
|
532
|
+
requiredSha256At(trust, "retentionPolicySha256", "evidenceTrust.retentionPolicySha256", errors);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
397
535
|
}
|
|
398
536
|
function requiredSha256At(value, key, path, errors) {
|
|
399
537
|
if (typeof value[key] !== "string" || !/^[0-9a-f]{64}$/.test(value[key]))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "witnora",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Independent assurance for covered agent action paths across models and frameworks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://witnora.com/",
|