@cassiomc1/forgeloop 1.6.2 → 1.6.4
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/DOCS_INDEX.md +26 -4
- package/EXECUTION_STATE.md +7 -0
- package/LOOP_ENGINEERING.md +58 -0
- package/LOOP_SYSTEM_DESIGN.md +54 -0
- package/ORCHESTRATOR_INTEGRATION.md +23 -0
- package/PROTOCOL_INTEGRATION.md +36 -0
- package/QUALITY_SCORECARD.md +21 -0
- package/README.md +57 -2
- package/TERMINOLOGY.md +11 -0
- package/THREAT_MODEL.md +7 -0
- package/completions/_forgeloop +85 -0
- package/completions/forgeloop.bash +173 -0
- package/completions/forgeloop.fish +544 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +281 -0
- package/docs/ARTIFACT_REFERENCE.md +226 -0
- package/docs/CLI_REFERENCE.md +308 -2
- package/docs/CODE_ATTESTATION.md +141 -0
- package/docs/CROSS_HARNESS_CONTINUITY.md +22 -2
- package/docs/DOCUMENTATION_GUIDE.md +13 -11
- package/docs/GETTING_STARTED.md +98 -1
- package/docs/MCP.md +8 -0
- package/docs/PLATFORM_ADAPTERS.md +69 -0
- package/docs/RECIPES.md +138 -0
- package/docs/RELEASE_CHECKLIST.md +54 -0
- package/docs/REVISION_PROVIDERS.md +136 -0
- package/docs/SIGNING_PROVIDERS.md +83 -0
- package/docs/TROUBLESHOOTING.md +184 -0
- package/docs/UNIVERSAL_INTEGRATION.md +11 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +13841 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +37 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +5046 -0
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +46 -43
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +41 -41
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +13783 -0
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +37 -0
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +4988 -0
- package/docs/diagrams/README.md +30 -22
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +125 -0
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +11 -8
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +116 -0
- package/docs/diagrams/manifest.json +42 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +20 -0
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +3 -3
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +20 -0
- package/integrations/generic-ci/verify.sh +14 -0
- package/package.json +31 -9
- package/schemas/attestation-verification-result.schema.json +34 -0
- package/schemas/code-attestation.schema.json +61 -0
- package/schemas/code-manifest.schema.json +57 -0
- package/schemas/config.schema.json +57 -1
- package/schemas/handoff-envelope.schema.json +57 -0
- package/schemas/in-toto-statement.schema.json +32 -0
- package/schemas/responsibility.schema.json +37 -0
- package/schemas/verification-scope.schema.json +26 -0
- package/schemas/workspace-binding.schema.json +20 -0
- package/scripts/CI_VALIDATORS.md +3 -0
- package/scripts/benchmark-cli-startup.mjs +60 -0
- package/scripts/check-changelog-freshness.mjs +88 -0
- package/scripts/check-critical-coverage.mjs +58 -0
- package/scripts/generate-agent-protocol-summary.mjs +141 -0
- package/scripts/generate-shell-completions.mjs +132 -0
- package/scripts/write-forgeloop-attestation-summary.mjs +93 -0
- package/src/cli.js +114 -6
- package/src/commands/attestation-create.js +30 -0
- package/src/commands/attestation-status.js +12 -0
- package/src/commands/attestation-verify-range.js +32 -0
- package/src/commands/attestation-verify.js +23 -0
- package/src/commands/handoff-create.js +9 -0
- package/src/commands/handoff-list.js +12 -0
- package/src/commands/handoff-show.js +10 -0
- package/src/commands/responsibility-set.js +20 -0
- package/src/commands/responsibility-status.js +12 -0
- package/src/commands/run-check.js +43 -3
- package/src/commands/task-repair-legacy-recovery.js +1 -2
- package/src/commands/verify-scope.js +9 -0
- package/src/commands/workspace-bind.js +17 -0
- package/src/commands/workspace-status.js +19 -0
- package/src/core/artifact-registry.js +84 -0
- package/src/core/attestation-coverage.js +134 -0
- package/src/core/attestation-verifier.js +227 -0
- package/src/core/attestation.js +263 -0
- package/src/core/audit.js +67 -1
- package/src/core/bundles.js +210 -9
- package/src/core/cli-command-definitions.js +196 -0
- package/src/core/code-manifest.js +293 -0
- package/src/core/command-executors.js +79 -0
- package/src/core/command-input.js +69 -18
- package/src/core/command-runtime.js +2 -1
- package/src/core/completion.js +170 -8
- package/src/core/config.js +60 -2
- package/src/core/continuity-cli-options.js +9 -3
- package/src/core/continuity-reconciliation.js +20 -0
- package/src/core/diagnostic-projection.js +3 -1
- package/src/core/error-codes.js +146 -0
- package/src/core/events.js +109 -13
- package/src/core/exit-codes.js +22 -0
- package/src/core/handoff.js +208 -0
- package/src/core/information-gain-projection.js +3 -3
- package/src/core/integration-invocation-policy.js +40 -0
- package/src/core/integration-resources.js +54 -0
- package/src/core/next-action-phases.js +968 -0
- package/src/core/next-action.js +32 -951
- package/src/core/phase.js +29 -1
- package/src/core/protocol-info.js +41 -1
- package/src/core/repository.js +256 -11
- package/src/core/responsibility.js +265 -0
- package/src/core/revision/git.js +207 -0
- package/src/core/revision/provider.js +90 -0
- package/src/core/revision/registry.js +5 -0
- package/src/core/route-artifact.js +1 -1
- package/src/core/schema-validation.js +8 -0
- package/src/core/signing/none.js +23 -0
- package/src/core/signing/provider.js +21 -0
- package/src/core/signing/registry.js +7 -0
- package/src/core/signing/sigstore.js +158 -0
- package/src/core/task-command.js +9 -1
- package/src/core/task-paths.js +72 -0
- package/src/core/templates.js +8 -0
- package/src/core/verification-scope-capability.js +179 -0
- package/src/core/verification-scope.js +271 -0
- package/src/core/workspace-binding.js +211 -0
- package/src/integration.d.ts +91 -0
- package/AGENT_COMPATIBILITY.md +0 -11
- package/docs/RELEASE_CHECKLIST_1_4.md +0 -38
- package/docs/RELEASE_CHECKLIST_1_5_MCP.md +0 -78
- package/docs/RELEASE_CHECKLIST_1_6_1.md +0 -121
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setResponsibilityContract } from "../core/responsibility.js";
|
|
2
|
+
|
|
3
|
+
export async function runResponsibilitySet(options = {}) {
|
|
4
|
+
return setResponsibilityContract(options.target, {
|
|
5
|
+
...options,
|
|
6
|
+
allowedPaths: options.responsibilityAllowedPaths?.length ? options.responsibilityAllowedPaths : undefined,
|
|
7
|
+
readOnlyPaths: options.responsibilityReadOnlyPaths?.length ? options.responsibilityReadOnlyPaths : undefined,
|
|
8
|
+
requiredCheckIds: options.responsibilityRequiredChecks?.length ? options.responsibilityRequiredChecks : undefined,
|
|
9
|
+
label: options.responsibilityLabel,
|
|
10
|
+
frozenInputs: {
|
|
11
|
+
contract: options.responsibilityFreezeContract,
|
|
12
|
+
route: options.responsibilityFreezeRoute,
|
|
13
|
+
claims: options.responsibilityFreezeClaims,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatResponsibilitySetResult(result) {
|
|
19
|
+
return `FORGELOOP RESPONSIBILITY SET\ntask: ${result.taskId}\nlabel: ${result.responsibility.label}\nfingerprint: ${result.fingerprint}\npath: ${result.path}\n\n`;
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { resolveResponsibilityStatus } from "../core/responsibility.js";
|
|
2
|
+
|
|
3
|
+
export async function runResponsibilityStatus({ target, packageRoot, taskId } = {}) {
|
|
4
|
+
return resolveResponsibilityStatus(target, { packageRoot, taskId });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatResponsibilityStatusResult(result) {
|
|
8
|
+
const lines = [`FORGELOOP RESPONSIBILITY: ${result.status}`, `task: ${result.taskId}`];
|
|
9
|
+
if (result.responsibility) lines.push(`label: ${result.responsibility.label}`);
|
|
10
|
+
for (const error of result.errors ?? []) lines.push(`${error.code}: ${error.message}`);
|
|
11
|
+
return `${lines.join("\n")}\n`;
|
|
12
|
+
}
|
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
} from "../core/completion-artifacts.js";
|
|
5
5
|
import { runCommandExecution } from "../core/execution.js";
|
|
6
6
|
import { withTaskMutation } from "../core/task-command.js";
|
|
7
|
+
import { readVerificationScope, validateVerificationScopeFreshness } from "../core/verification-scope.js";
|
|
8
|
+
import { bindVerificationScopeCommand } from "../core/verification-scope-capability.js";
|
|
7
9
|
|
|
8
10
|
export async function runCheck({
|
|
9
11
|
target,
|
|
@@ -13,6 +15,7 @@ export async function runCheck({
|
|
|
13
15
|
argv,
|
|
14
16
|
details,
|
|
15
17
|
timeoutMs,
|
|
18
|
+
scopeRef,
|
|
16
19
|
authorityContext,
|
|
17
20
|
runtimeContext,
|
|
18
21
|
taskId,
|
|
@@ -25,6 +28,43 @@ export async function runCheck({
|
|
|
25
28
|
}
|
|
26
29
|
return withTaskMutation(target, { taskId: taskId ?? task, packageRoot }, "run-check", async (ctx) => {
|
|
27
30
|
const effectiveTaskId = ctx?.taskId ?? null;
|
|
31
|
+
let executionDetails = details;
|
|
32
|
+
let commandArgv = [...argv];
|
|
33
|
+
if (scopeRef) {
|
|
34
|
+
const scope = await readVerificationScope(target, {
|
|
35
|
+
packageRoot,
|
|
36
|
+
taskId: effectiveTaskId,
|
|
37
|
+
scopePath: scopeRef,
|
|
38
|
+
});
|
|
39
|
+
const freshness = await validateVerificationScopeFreshness(target, {
|
|
40
|
+
packageRoot,
|
|
41
|
+
taskId: effectiveTaskId,
|
|
42
|
+
scope: scope.value,
|
|
43
|
+
});
|
|
44
|
+
const binding = await bindVerificationScopeCommand({
|
|
45
|
+
target,
|
|
46
|
+
packageRoot,
|
|
47
|
+
checkId: id,
|
|
48
|
+
argv: commandArgv,
|
|
49
|
+
scope: scope.value,
|
|
50
|
+
capabilities: freshness.current.checkerCapabilities,
|
|
51
|
+
});
|
|
52
|
+
commandArgv = binding.argv;
|
|
53
|
+
executionDetails = {
|
|
54
|
+
...(details ?? {}),
|
|
55
|
+
verificationScope: {
|
|
56
|
+
ref: scope.path,
|
|
57
|
+
fingerprint: scope.fingerprint,
|
|
58
|
+
mode: scope.value.resolvedMode,
|
|
59
|
+
selectedPaths: scope.value.selectedPaths,
|
|
60
|
+
argv: [...commandArgv],
|
|
61
|
+
...(binding.checker ? {
|
|
62
|
+
checkerId: binding.checker.checkId,
|
|
63
|
+
checkerCapabilityFingerprint: binding.capabilityFingerprint,
|
|
64
|
+
} : {}),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
28
68
|
const ready = await assertRecordCheckPrerequisites({
|
|
29
69
|
target,
|
|
30
70
|
packageRoot,
|
|
@@ -43,8 +83,8 @@ export async function runCheck({
|
|
|
43
83
|
checkId: id,
|
|
44
84
|
requirement,
|
|
45
85
|
verificationCycle,
|
|
46
|
-
argv,
|
|
47
|
-
details,
|
|
86
|
+
argv: commandArgv,
|
|
87
|
+
details: executionDetails,
|
|
48
88
|
timeoutMs,
|
|
49
89
|
authorityContext,
|
|
50
90
|
runtimeContext,
|
|
@@ -62,7 +102,7 @@ export async function runCheck({
|
|
|
62
102
|
command: execution.execution.argv.join(" "),
|
|
63
103
|
result: execution.result,
|
|
64
104
|
...(execution.execution.exitCode === null ? {} : { exitCode: execution.execution.exitCode }),
|
|
65
|
-
details,
|
|
105
|
+
details: executionDetails,
|
|
66
106
|
executionRef: execution.execution.executionId,
|
|
67
107
|
provenance: "FORGELOOP_EXECUTED",
|
|
68
108
|
authorityContext,
|
|
@@ -195,7 +195,6 @@ export async function runTaskRepairLegacyRecovery({ target, packageRoot, taskId,
|
|
|
195
195
|
|
|
196
196
|
const existingRecovery = await readTaskRecovery(target, { taskId: effectiveTaskId, packageRoot });
|
|
197
197
|
const locked = await inspectLegacyRecoveryMigration(target, { taskId: effectiveTaskId, packageRoot });
|
|
198
|
-
let candidate;
|
|
199
198
|
if (locked.alreadyMigrated) {
|
|
200
199
|
return verifyAlreadyRepaired(target, {
|
|
201
200
|
taskId: effectiveTaskId,
|
|
@@ -204,7 +203,7 @@ export async function runTaskRepairLegacyRecovery({ target, packageRoot, taskId,
|
|
|
204
203
|
existingRecovery,
|
|
205
204
|
});
|
|
206
205
|
}
|
|
207
|
-
candidate = requireSingleTailCandidate({
|
|
206
|
+
const candidate = requireSingleTailCandidate({
|
|
208
207
|
taskId: effectiveTaskId,
|
|
209
208
|
events: locked.events,
|
|
210
209
|
candidates: locked.candidates,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { captureVerificationScope } from "../core/verification-scope.js";
|
|
2
|
+
|
|
3
|
+
export async function runVerifyScope({ target, packageRoot, taskId, verificationScopeMode } = {}) {
|
|
4
|
+
return captureVerificationScope(target, { packageRoot, taskId, mode: verificationScopeMode ?? "AUTO" });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatVerifyScopeResult(result) {
|
|
8
|
+
return `FORGELOOP VERIFICATION SCOPE: ${result.scope.resolvedMode}\ntask: ${result.scope.taskId}\nselected paths: ${result.scope.selectedPaths.length}\npath: ${result.path}\n\n`;
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { bindTaskWorkspace } from "../core/workspace-binding.js";
|
|
2
|
+
|
|
3
|
+
export async function runWorkspaceBind({ target, packageRoot, taskId, now } = {}) {
|
|
4
|
+
return bindTaskWorkspace(target, { taskId, packageRoot, now });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatWorkspaceBindResult(result) {
|
|
8
|
+
return [
|
|
9
|
+
`FORGELOOP WORKSPACE BIND: ${result.status}`,
|
|
10
|
+
`task: ${result.taskId}`,
|
|
11
|
+
`binding: ${result.path}`,
|
|
12
|
+
`workspace: ${result.binding.workspaceIdentity}`,
|
|
13
|
+
`repository: ${result.binding.repositoryIdentity}`,
|
|
14
|
+
`already bound: ${result.alreadyBound ? "yes" : "no"}`,
|
|
15
|
+
"",
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { resolveWorkspaceBindingStatus } from "../core/workspace-binding.js";
|
|
2
|
+
|
|
3
|
+
export async function runWorkspaceStatus({ target, packageRoot, taskId } = {}) {
|
|
4
|
+
return resolveWorkspaceBindingStatus(target, { taskId, packageRoot });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatWorkspaceStatusResult(result) {
|
|
8
|
+
const lines = [
|
|
9
|
+
`FORGELOOP WORKSPACE: ${result.status}`,
|
|
10
|
+
`task: ${result.taskId}`,
|
|
11
|
+
`binding: ${result.path}`,
|
|
12
|
+
];
|
|
13
|
+
if (result.binding) {
|
|
14
|
+
lines.push(`workspace: ${result.binding.workspaceIdentity}`);
|
|
15
|
+
lines.push(`repository: ${result.binding.repositoryIdentity}`);
|
|
16
|
+
}
|
|
17
|
+
if (result.error) lines.push(`${result.error.code}: ${result.error.message}`);
|
|
18
|
+
return `${lines.join("\n")}\n`;
|
|
19
|
+
}
|
|
@@ -283,4 +283,88 @@ export const ARTIFACT_REGISTRY = Object.freeze({
|
|
|
283
283
|
isPersisted: true,
|
|
284
284
|
description: "Immutable trajectory evaluation results compiled from the canonical trace against a local reference scenario.",
|
|
285
285
|
}),
|
|
286
|
+
workspaceBinding: Object.freeze({
|
|
287
|
+
key: "workspaceBinding",
|
|
288
|
+
scope: "TASK",
|
|
289
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.workspaceBinding}`,
|
|
290
|
+
schema: "workspace-binding",
|
|
291
|
+
owner: "PROTOCOL_GENERATED",
|
|
292
|
+
mutability: "IMMUTABLE_AFTER_BIND",
|
|
293
|
+
trustRole: "WORKSPACE_IDENTITY_BINDING",
|
|
294
|
+
isPublic: true,
|
|
295
|
+
isPersisted: true,
|
|
296
|
+
description: "Optional task binding to the exact Git worktree identity used for ForgeLoop mutation and verification.",
|
|
297
|
+
}),
|
|
298
|
+
handoffs: Object.freeze({
|
|
299
|
+
key: "handoffs",
|
|
300
|
+
scope: "TASK",
|
|
301
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.handoffs}/handoff-<id>.json`,
|
|
302
|
+
schema: "handoff-envelope",
|
|
303
|
+
owner: "PROTOCOL_COMPILED",
|
|
304
|
+
mutability: "IMMUTABLE_ONCE_WRITTEN",
|
|
305
|
+
trustRole: "CANONICAL_HANDOFF_SNAPSHOT",
|
|
306
|
+
isPublic: true,
|
|
307
|
+
isPersisted: true,
|
|
308
|
+
description: "Immutable protocol-derived handoff snapshots; not delegation, review, completion, or authority evidence.",
|
|
309
|
+
}),
|
|
310
|
+
responsibility: Object.freeze({
|
|
311
|
+
key: "responsibility",
|
|
312
|
+
scope: "TASK",
|
|
313
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.responsibility}`,
|
|
314
|
+
schema: "responsibility",
|
|
315
|
+
owner: "AGENT_OR_HARNESS_CONSTRAINED",
|
|
316
|
+
mutability: "IMMUTABLE_DURING_PASS",
|
|
317
|
+
trustRole: "RESPONSIBILITY_CONSTRAINT",
|
|
318
|
+
isPublic: true,
|
|
319
|
+
isPersisted: true,
|
|
320
|
+
description: "Optional mechanically verifiable pass constraints for allowed, read-only, frozen, and required-check paths.",
|
|
321
|
+
}),
|
|
322
|
+
verificationScope: Object.freeze({
|
|
323
|
+
key: "verificationScope",
|
|
324
|
+
scope: "TASK",
|
|
325
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.verificationScope}`,
|
|
326
|
+
schema: "verification-scope",
|
|
327
|
+
owner: "PROTOCOL_COMPILED",
|
|
328
|
+
mutability: "RECOMPUTED_ON_SCOPE_CHANGE",
|
|
329
|
+
trustRole: "VERIFICATION_SCOPE_PLAN",
|
|
330
|
+
isPublic: true,
|
|
331
|
+
isPersisted: true,
|
|
332
|
+
description: "Read-only planning boundary for changed, claimed, full, or explicitly unresolved verification.",
|
|
333
|
+
}),
|
|
334
|
+
codeManifest: Object.freeze({
|
|
335
|
+
key: "codeManifest",
|
|
336
|
+
scope: "TASK",
|
|
337
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.attestations}/code-manifest.json`,
|
|
338
|
+
schema: "code-manifest",
|
|
339
|
+
owner: "PROTOCOL_GENERATED",
|
|
340
|
+
mutability: "IMMUTABLE_ONCE_WRITTEN",
|
|
341
|
+
trustRole: "CONTENT_INTEGRITY_SNAPSHOT",
|
|
342
|
+
isPublic: true,
|
|
343
|
+
isPersisted: true,
|
|
344
|
+
description: "ForgeLoop SHA-256 source-content snapshot bound to final completion evidence.",
|
|
345
|
+
}),
|
|
346
|
+
attestationStatement: Object.freeze({
|
|
347
|
+
key: "attestationStatement",
|
|
348
|
+
scope: "TASK",
|
|
349
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.attestations}/statement.json`,
|
|
350
|
+
schema: "in-toto-statement",
|
|
351
|
+
owner: "PROTOCOL_COMPILED",
|
|
352
|
+
mutability: "IMMUTABLE_ONCE_WRITTEN",
|
|
353
|
+
trustRole: "CODE_ATTESTATION_STATEMENT",
|
|
354
|
+
isPublic: true,
|
|
355
|
+
isPersisted: true,
|
|
356
|
+
description: "Deterministic in-toto Statement v1 predicate binding source content to ForgeLoop evidence.",
|
|
357
|
+
}),
|
|
358
|
+
attestationBundle: Object.freeze({
|
|
359
|
+
key: "attestationBundle",
|
|
360
|
+
scope: "TASK",
|
|
361
|
+
path: `${TASK_STATE_ROOT}/<task-key>/${TASK_ARTIFACT_FILES.attestations}/statement.sigstore.json`,
|
|
362
|
+
schema: null,
|
|
363
|
+
owner: "EXTERNAL_SIGNING_PROVIDER",
|
|
364
|
+
mutability: "EXTERNAL_IMMUTABLE",
|
|
365
|
+
trustRole: "EXTERNAL_SIGNATURE_BUNDLE",
|
|
366
|
+
isPublic: true,
|
|
367
|
+
isPersisted: true,
|
|
368
|
+
description: "Optional external signing-provider bundle; existence alone never establishes a valid signature.",
|
|
369
|
+
}),
|
|
286
370
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { getPackageRoot } from "./templates.js";
|
|
2
|
+
import { discoverTasks } from "./task-discovery.js";
|
|
3
|
+
import { readCodeManifest, validateCodeManifestBindings } from "./code-manifest.js";
|
|
4
|
+
import { readAttestationStatement } from "./attestation.js";
|
|
5
|
+
import { resolveRevisionProvider } from "./revision/provider.js";
|
|
6
|
+
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
7
|
+
import { assertAttestationStatementBindings, verifyCodeManifestContent } from "./attestation-verifier.js";
|
|
8
|
+
import { taskAttestationBundlePath } from "./task-paths.js";
|
|
9
|
+
|
|
10
|
+
function pathMatchesExclusion(pathValue, exclusions) {
|
|
11
|
+
return exclusions.some((pattern) => {
|
|
12
|
+
const normalized = String(pattern).replaceAll("\\", "/");
|
|
13
|
+
if (normalized.endsWith("/**")) return pathValue === normalized.slice(0, -3) || pathValue.startsWith(normalized.slice(0, -2));
|
|
14
|
+
return pathValue === normalized;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function changedPathNames(entry) {
|
|
19
|
+
return [entry.path, entry.sourcePath].filter(Boolean);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isProtocolMetadataPath(value) {
|
|
23
|
+
const normalized = String(value).replaceAll("\\", "/").replace(/^\.\//u, "");
|
|
24
|
+
return normalized === ".forgeloop" || normalized.startsWith(".forgeloop/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function evaluateAttestationCoverage({
|
|
28
|
+
target,
|
|
29
|
+
packageRoot = getPackageRoot(),
|
|
30
|
+
revisionProvider = "git",
|
|
31
|
+
baseRevision,
|
|
32
|
+
headRevision,
|
|
33
|
+
requireCompleteCoverage = true,
|
|
34
|
+
requireSignature = false,
|
|
35
|
+
signingProvider = null,
|
|
36
|
+
signerPolicy = {},
|
|
37
|
+
exclusions = [],
|
|
38
|
+
} = {}) {
|
|
39
|
+
const provider = typeof revisionProvider === "string"
|
|
40
|
+
? await resolveRevisionProvider({ target, providerName: revisionProvider, registry: REVISION_PROVIDERS })
|
|
41
|
+
: revisionProvider;
|
|
42
|
+
const changedEntries = await provider.getChangedEntries({ target, baseRevision, headRevision });
|
|
43
|
+
const changedPaths = [...new Set(changedEntries.flatMap(changedPathNames)
|
|
44
|
+
.filter((value) => !isProtocolMetadataPath(value)))].sort();
|
|
45
|
+
const validAttestations = [];
|
|
46
|
+
const errors = [];
|
|
47
|
+
const tasks = await discoverTasks(target, packageRoot);
|
|
48
|
+
for (const task of tasks.filter((item) => item.healthy !== false && item.taskId)) {
|
|
49
|
+
try {
|
|
50
|
+
const statement = await readAttestationStatement({ target, packageRoot, taskId: task.taskId });
|
|
51
|
+
const manifest = await readCodeManifest({ target, packageRoot, taskId: task.taskId });
|
|
52
|
+
await validateCodeManifestBindings({ target, packageRoot, taskId: task.taskId, manifest: manifest.value, revisionProvider: provider });
|
|
53
|
+
assertAttestationStatementBindings(statement.value, manifest.value, task.taskId, manifest.fingerprint);
|
|
54
|
+
await verifyCodeManifestContent({ target, manifest: manifest.value, revisionProvider: provider, revision: headRevision });
|
|
55
|
+
let signatureStatus = "UNSIGNED";
|
|
56
|
+
if (requireSignature) {
|
|
57
|
+
const { verifyAttestation } = await import("./attestation-verifier.js");
|
|
58
|
+
const verified = await verifyAttestation({
|
|
59
|
+
target,
|
|
60
|
+
packageRoot,
|
|
61
|
+
taskId: task.taskId,
|
|
62
|
+
revision: headRevision,
|
|
63
|
+
revisionProvider: provider,
|
|
64
|
+
requireSignature: true,
|
|
65
|
+
signingProvider,
|
|
66
|
+
bundlePath: taskAttestationBundlePath(task.taskId),
|
|
67
|
+
identity: signerPolicy.identity,
|
|
68
|
+
issuer: signerPolicy.issuer,
|
|
69
|
+
trustedRoot: signerPolicy.trustedRoot,
|
|
70
|
+
});
|
|
71
|
+
signatureStatus = verified.signature.status;
|
|
72
|
+
if (verified.status !== "VALID") {
|
|
73
|
+
const error = new Error(verified.errors[0]?.message ?? "Signature verification failed");
|
|
74
|
+
error.code = verified.errors[0]?.code ?? "E_ATTESTATION_SIGNATURE_INVALID";
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
validAttestations.push({
|
|
79
|
+
taskId: task.taskId,
|
|
80
|
+
statement,
|
|
81
|
+
manifest: manifest.value,
|
|
82
|
+
signatureStatus,
|
|
83
|
+
});
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code !== "E_ATTESTATION_STATEMENT_MISSING" && error.code !== "E_ATTESTATION_MANIFEST_MISSING") {
|
|
86
|
+
errors.push({ taskId: task.taskId, code: error.code ?? "E_ATTESTATION_INVALID", message: error.message });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const coveredByPath = new Map();
|
|
91
|
+
const overlaps = [];
|
|
92
|
+
for (const attestation of validAttestations) {
|
|
93
|
+
for (const entry of attestation.manifest.entries) {
|
|
94
|
+
for (const name of changedPathNames(entry)) {
|
|
95
|
+
const digest = entry.kind === "DELETED" ? "DELETED" : entry.sha256 ?? entry.providerContentId ?? "UNKNOWN";
|
|
96
|
+
const existing = coveredByPath.get(name);
|
|
97
|
+
if (existing && existing.digest !== digest) {
|
|
98
|
+
errors.push({ code: "E_ATTESTATION_COVERAGE_CONFLICT", message: `Conflicting attestation content for ${name}`, path: name, tasks: [existing.taskId, attestation.taskId] });
|
|
99
|
+
} else if (existing) {
|
|
100
|
+
overlaps.push({ path: name, tasks: [...new Set([existing.taskId, attestation.taskId])] });
|
|
101
|
+
} else {
|
|
102
|
+
coveredByPath.set(name, { taskId: attestation.taskId, digest });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const excluded = changedPaths.filter((item) => pathMatchesExclusion(item, exclusions));
|
|
108
|
+
const uncoveredPaths = changedPaths.filter((item) => !pathMatchesExclusion(item, exclusions) && !coveredByPath.has(item));
|
|
109
|
+
if (requireCompleteCoverage && uncoveredPaths.length > 0) errors.push({ code: "E_ATTESTATION_COVERAGE_GAP", message: `Changed source paths are not covered by valid attestations: ${uncoveredPaths.join(", ")}`, paths: uncoveredPaths });
|
|
110
|
+
const status = errors.length === 0 && (!requireCompleteCoverage || uncoveredPaths.length === 0) ? "VALID" : "INVALID";
|
|
111
|
+
const signatureStatus = requireSignature
|
|
112
|
+
? (validAttestations.every((item) => item.signatureStatus === "VALID") && (validAttestations.length > 0 || changedPaths.length === 0) ? "VALID" : "INVALID")
|
|
113
|
+
: "UNSIGNED";
|
|
114
|
+
return {
|
|
115
|
+
schemaVersion: 1,
|
|
116
|
+
status,
|
|
117
|
+
level: status === "VALID" ? (requireSignature ? "ATTESTED" : "VERIFIED") : "PROCESSED",
|
|
118
|
+
revisionProvider: provider.name ?? "unknown",
|
|
119
|
+
baseRevision,
|
|
120
|
+
headRevision,
|
|
121
|
+
changedPaths: changedPaths.length,
|
|
122
|
+
coveredPaths: [...coveredByPath.keys()].filter((item) => !excluded.includes(item)).length,
|
|
123
|
+
uncoveredPaths,
|
|
124
|
+
tasks: validAttestations.length,
|
|
125
|
+
overlaps,
|
|
126
|
+
errors,
|
|
127
|
+
signature: {
|
|
128
|
+
required: requireSignature,
|
|
129
|
+
status: signatureStatus,
|
|
130
|
+
...(signerPolicy.identity ? { identity: signerPolicy.identity } : {}),
|
|
131
|
+
...(signerPolicy.issuer ? { issuer: signerPolicy.issuer } : {}),
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { canonicalFingerprint } from "./artifacts.js";
|
|
4
|
+
import { getPackageRoot } from "./templates.js";
|
|
5
|
+
import { readCodeManifest, validateCodeManifestBindings } from "./code-manifest.js";
|
|
6
|
+
import { readAttestationStatement, validateAttestationStatement } from "./attestation.js";
|
|
7
|
+
import { assertRevisionProvider, resolveRevisionProvider } from "./revision/provider.js";
|
|
8
|
+
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
9
|
+
import { assertSigningProvider, resolveSigningProvider } from "./signing/provider.js";
|
|
10
|
+
import { SIGNING_PROVIDERS } from "./signing/registry.js";
|
|
11
|
+
import { taskAttestationBundlePath, taskAttestationStatementPath } from "./task-paths.js";
|
|
12
|
+
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
13
|
+
import { E_ATTESTATION_CONFIGURATION_INVALID } from "./error-codes.js";
|
|
14
|
+
|
|
15
|
+
function verifierError(code, message, artifacts = []) {
|
|
16
|
+
const error = new Error(message);
|
|
17
|
+
error.name = "AttestationVerificationError";
|
|
18
|
+
error.code = code;
|
|
19
|
+
error.artifacts = artifacts;
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function sha256(bytes) {
|
|
24
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function assertAttestationStatementBindings(statement, manifest, taskId, manifestFingerprint = canonicalFingerprint(manifest)) {
|
|
28
|
+
const predicate = statement.predicate;
|
|
29
|
+
if (predicate.task.taskId !== taskId || predicate.task.verificationCycle !== manifest.verificationCycle) {
|
|
30
|
+
throw verifierError("E_ATTESTATION_SCOPE_MISMATCH", "Attestation statement task or verification cycle does not match the manifest");
|
|
31
|
+
}
|
|
32
|
+
if (predicate.content.manifestFingerprint !== manifestFingerprint) {
|
|
33
|
+
throw verifierError("E_ATTESTATION_SUBJECT_MISMATCH", "Statement does not bind the current code manifest");
|
|
34
|
+
}
|
|
35
|
+
if (predicate.content.contentDigest !== manifest.contentDigest) {
|
|
36
|
+
throw verifierError("E_ATTESTATION_SUBJECT_MISMATCH", "Statement subject does not bind the manifest content digest");
|
|
37
|
+
}
|
|
38
|
+
const expectedPaths = [...new Set(manifest.entries.flatMap((entry) => [entry.path, entry.sourcePath].filter(Boolean)))].sort();
|
|
39
|
+
const actualPaths = [...new Set(predicate.content.coveredPaths)].sort();
|
|
40
|
+
if (JSON.stringify(expectedPaths) !== JSON.stringify(actualPaths)) {
|
|
41
|
+
throw verifierError("E_ATTESTATION_SCOPE_MISMATCH", "Statement covered paths do not match the manifest");
|
|
42
|
+
}
|
|
43
|
+
const bindingPairs = [
|
|
44
|
+
["contractFingerprint", "E_ATTESTATION_CONTRACT_MISMATCH"],
|
|
45
|
+
["routeFingerprint", "E_ATTESTATION_ROUTE_MISMATCH"],
|
|
46
|
+
["stateFingerprint", "E_ATTESTATION_STATE_MISMATCH"],
|
|
47
|
+
["receiptFingerprint", "E_ATTESTATION_RECEIPT_MISMATCH"],
|
|
48
|
+
["ledgerSeq", "E_ATTESTATION_LEDGER_MISMATCH"],
|
|
49
|
+
["ledgerHash", "E_ATTESTATION_LEDGER_MISMATCH"],
|
|
50
|
+
];
|
|
51
|
+
for (const [key, code] of bindingPairs) {
|
|
52
|
+
if (predicate.evidence[key] !== manifest.bindings[key]) {
|
|
53
|
+
throw verifierError(code, `Statement evidence does not match the manifest binding: ${key}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function verifyEntryContent(provider, { target, revision }, entry) {
|
|
59
|
+
if (entry.kind === "DELETED" || entry.operation === "DELETED") {
|
|
60
|
+
try {
|
|
61
|
+
await provider.readContent({ target, revision, path: entry.path });
|
|
62
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Deleted path exists at revision ${revision}: ${entry.path}`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code === "E_ATTESTATION_CONTENT_MISMATCH") throw error;
|
|
65
|
+
if (error.notFound === true) return { valid: true };
|
|
66
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Unable to prove that deleted path is absent at revision ${revision}: ${entry.path}`, [entry.path]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (entry.kind === "GITLINK") {
|
|
70
|
+
let identity;
|
|
71
|
+
try {
|
|
72
|
+
identity = await provider.getContentIdentity({ target, revision, path: entry.path });
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error.notFound === true) throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Gitlink is absent at revision ${revision}: ${entry.path}`, [entry.path]);
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
if (identity !== entry.providerContentId) {
|
|
78
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Gitlink identity differs from the attested snapshot: ${entry.path}`, [entry.path]);
|
|
79
|
+
}
|
|
80
|
+
return { valid: true, actualIdentity: identity };
|
|
81
|
+
}
|
|
82
|
+
let bytes;
|
|
83
|
+
try {
|
|
84
|
+
bytes = await provider.readContent({ target, revision, path: entry.path });
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.code?.startsWith("E_REVISION_") && error.notFound !== true) throw error;
|
|
87
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Attested path cannot be read at revision ${revision}: ${entry.path}`, [entry.path]);
|
|
88
|
+
}
|
|
89
|
+
const actualDigest = sha256(bytes);
|
|
90
|
+
if (actualDigest !== entry.sha256) {
|
|
91
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Current source content differs from the attested snapshot: ${entry.path}`, [entry.path]);
|
|
92
|
+
}
|
|
93
|
+
if (entry.providerContentId) {
|
|
94
|
+
let actualIdentity;
|
|
95
|
+
try {
|
|
96
|
+
actualIdentity = await provider.getContentIdentity({ target, revision, path: entry.path });
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error.code?.startsWith("E_REVISION_") && error.notFound !== true) throw error;
|
|
99
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Attested provider identity cannot be read at revision ${revision}: ${entry.path}`, [entry.path]);
|
|
100
|
+
}
|
|
101
|
+
if (actualIdentity !== entry.providerContentId) {
|
|
102
|
+
throw verifierError("E_ATTESTATION_CONTENT_MISMATCH", `Provider content identity differs from the attested snapshot: ${entry.path}`, [entry.path]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { valid: true, actualDigest };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function verifyCodeManifestContent({ target, manifest, revisionProvider, revision = null } = {}) {
|
|
109
|
+
const provider = typeof revisionProvider === "string"
|
|
110
|
+
? await resolveRevisionProvider({ target, providerName: revisionProvider, registry: REVISION_PROVIDERS })
|
|
111
|
+
: revisionProvider;
|
|
112
|
+
assertRevisionProvider(provider);
|
|
113
|
+
const targetRevision = revision ?? (manifest.capture.mode === "WORKTREE" ? "WORKTREE" : manifest.capture.observedRevision);
|
|
114
|
+
const results = [];
|
|
115
|
+
for (const entry of manifest.entries) {
|
|
116
|
+
results.push({ path: entry.path, ...(await verifyEntryContent(provider, { target, revision: targetRevision }, entry)) });
|
|
117
|
+
}
|
|
118
|
+
return { valid: true, revision: targetRevision, results, provider };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function verifyAttestation({
|
|
122
|
+
target,
|
|
123
|
+
packageRoot = getPackageRoot(),
|
|
124
|
+
taskId,
|
|
125
|
+
revision = null,
|
|
126
|
+
bundlePath = null,
|
|
127
|
+
identity = null,
|
|
128
|
+
issuer = null,
|
|
129
|
+
revisionProvider = null,
|
|
130
|
+
signingProvider = null,
|
|
131
|
+
trustedRoot = null,
|
|
132
|
+
requireSignature = false,
|
|
133
|
+
} = {}) {
|
|
134
|
+
let effectiveRequireSignature = requireSignature === true;
|
|
135
|
+
let effectiveSigningProvider = signingProvider;
|
|
136
|
+
let effectiveBundlePath = bundlePath;
|
|
137
|
+
let configuredPolicy = {};
|
|
138
|
+
let configurationError = null;
|
|
139
|
+
try {
|
|
140
|
+
const { readConfig } = await import("./config.js");
|
|
141
|
+
const config = await readConfig(target, packageRoot);
|
|
142
|
+
const signing = config.attestation?.signing ?? {};
|
|
143
|
+
effectiveRequireSignature ||= signing.required === true;
|
|
144
|
+
effectiveSigningProvider ??= signing.provider;
|
|
145
|
+
configuredPolicy = signing.policy ?? {};
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (error.code !== "ARTIFACT_MISSING") configurationError = error;
|
|
148
|
+
}
|
|
149
|
+
if (effectiveRequireSignature && !effectiveBundlePath) effectiveBundlePath = taskAttestationBundlePath(taskId);
|
|
150
|
+
const result = {
|
|
151
|
+
schemaVersion: 1,
|
|
152
|
+
status: "INVALID",
|
|
153
|
+
level: "PROCESSED",
|
|
154
|
+
taskId,
|
|
155
|
+
revisionProvider: typeof revisionProvider === "string" ? revisionProvider : "git",
|
|
156
|
+
revision: revision ?? null,
|
|
157
|
+
content: { status: "NOT_CHECKED", checkedPaths: 0 },
|
|
158
|
+
receipt: "NOT_CHECKED",
|
|
159
|
+
statement: "NOT_CHECKED",
|
|
160
|
+
ledger: "NOT_CHECKED",
|
|
161
|
+
signature: { required: effectiveRequireSignature, status: effectiveRequireSignature ? "UNAVAILABLE" : "NOT_CHECKED" },
|
|
162
|
+
errors: [],
|
|
163
|
+
};
|
|
164
|
+
if (configurationError) {
|
|
165
|
+
result.errors.push({ code: E_ATTESTATION_CONFIGURATION_INVALID, message: configurationError.message });
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const statementArtifact = await readAttestationStatement({ target, packageRoot, taskId });
|
|
170
|
+
const manifestArtifact = await readCodeManifest({ target, packageRoot, taskId });
|
|
171
|
+
await validateAttestationStatement(statementArtifact.value, packageRoot);
|
|
172
|
+
result.statement = "VALID";
|
|
173
|
+
result.ledger = "VALID";
|
|
174
|
+
result.receipt = "VALID";
|
|
175
|
+
assertAttestationStatementBindings(
|
|
176
|
+
statementArtifact.value,
|
|
177
|
+
manifestArtifact.value,
|
|
178
|
+
taskId,
|
|
179
|
+
manifestArtifact.fingerprint,
|
|
180
|
+
);
|
|
181
|
+
const provider = typeof revisionProvider === "string"
|
|
182
|
+
? await resolveRevisionProvider({ target, providerName: revisionProvider, registry: REVISION_PROVIDERS })
|
|
183
|
+
: revisionProvider ?? await resolveRevisionProvider({ target, providerName: manifestArtifact.value.capture.revisionProvider, registry: REVISION_PROVIDERS });
|
|
184
|
+
result.revisionProvider = provider.name ?? result.revisionProvider;
|
|
185
|
+
await validateCodeManifestBindings({ target, packageRoot, taskId, manifest: manifestArtifact.value, revisionProvider: provider });
|
|
186
|
+
const content = await verifyCodeManifestContent({ target, manifest: manifestArtifact.value, revisionProvider: provider, revision });
|
|
187
|
+
result.revision = content.revision;
|
|
188
|
+
result.content = { status: "VALID", checkedPaths: content.results.length };
|
|
189
|
+
const signingPolicy = {
|
|
190
|
+
...configuredPolicy,
|
|
191
|
+
identity,
|
|
192
|
+
issuer,
|
|
193
|
+
...(trustedRoot ? { trustedRoot } : {}),
|
|
194
|
+
};
|
|
195
|
+
if (effectiveRequireSignature && !(await fileExists(ensureWithin(target, effectiveBundlePath)))) {
|
|
196
|
+
throw verifierError("E_ATTESTATION_UNSIGNED", `Required attestation signature bundle is missing: ${effectiveBundlePath}`);
|
|
197
|
+
}
|
|
198
|
+
const signer = typeof effectiveSigningProvider === "object"
|
|
199
|
+
? assertSigningProvider(effectiveSigningProvider)
|
|
200
|
+
: await resolveSigningProvider({
|
|
201
|
+
providerName: effectiveSigningProvider ?? (effectiveBundlePath ? "sigstore" : "none"),
|
|
202
|
+
registry: SIGNING_PROVIDERS,
|
|
203
|
+
});
|
|
204
|
+
const signature = await signer.verify({
|
|
205
|
+
target,
|
|
206
|
+
statementPath: taskAttestationStatementPath(taskId),
|
|
207
|
+
bundlePath: effectiveBundlePath,
|
|
208
|
+
policy: signingPolicy,
|
|
209
|
+
});
|
|
210
|
+
result.signature = {
|
|
211
|
+
required: effectiveRequireSignature,
|
|
212
|
+
status: signature.status,
|
|
213
|
+
provider: signer.name,
|
|
214
|
+
...(identity ? { identity } : {}),
|
|
215
|
+
...(issuer ? { issuer } : {}),
|
|
216
|
+
};
|
|
217
|
+
if (effectiveRequireSignature && signature.status !== "VALID") {
|
|
218
|
+
throw verifierError(signature.code ?? "E_ATTESTATION_SIGNATURE_INVALID", signature.message ?? "Required signature is not valid");
|
|
219
|
+
}
|
|
220
|
+
result.status = "VALID";
|
|
221
|
+
result.level = signature.status === "VALID" ? "ATTESTED" : "VERIFIED";
|
|
222
|
+
return result;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
result.errors.push({ code: error.code ?? "E_ATTESTATION_STATEMENT_INVALID", message: error.message, ...(error.artifacts ? { artifacts: error.artifacts } : {}) });
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
}
|