@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
|
@@ -278,6 +278,7 @@ export const CLI_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
278
278
|
"--requirement": Object.freeze({ targetKey: "checkRequirement", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--requirement requires an evidence target", description: "completion requirement covered by the check" }),
|
|
279
279
|
"--details": Object.freeze({ targetKey: "checkDetails", parseType: "json-object", takesValue: true, valueName: "json", missingValueMessage: "--details requires a JSON object", description: "additional structured check details" }),
|
|
280
280
|
"--timeout-ms": Object.freeze({ targetKey: "timeoutMs", parseType: "non-negative-integer", takesValue: true, valueName: "number", missingValueMessage: "--timeout-ms requires a non-negative integer", description: "maximum command duration before termination" }),
|
|
281
|
+
"--scope-ref": Object.freeze({ targetKey: "scopeRef", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--scope-ref requires a path", description: "current verification-scope.json to bind to execution evidence" }),
|
|
281
282
|
"--": Object.freeze({ targetKey: "commandArgv", parseType: "argv", takesValue: true, valueName: "argv...", description: "exact command argv to classify, execute, and attest" }),
|
|
282
283
|
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit structured output as JSON" }),
|
|
283
284
|
}),
|
|
@@ -1026,6 +1027,201 @@ export const CLI_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
1026
1027
|
mayExecuteExternalProcess: false,
|
|
1027
1028
|
description: "Reacquires a recovered task's write claims under project serialization and restores ordinary mutation authority.",
|
|
1028
1029
|
}),
|
|
1030
|
+
"workspace-bind": Object.freeze({
|
|
1031
|
+
name: "workspace-bind",
|
|
1032
|
+
category: "workspace",
|
|
1033
|
+
mutation: "MUTATING",
|
|
1034
|
+
options: Object.freeze({
|
|
1035
|
+
...CLI_COMMON_OPTIONS,
|
|
1036
|
+
...CLI_TASK_OPTION,
|
|
1037
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit binding result as JSON" }),
|
|
1038
|
+
}),
|
|
1039
|
+
writes: [".forgeloop/task-state/<taskKey>/workspace-binding.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
|
|
1040
|
+
removes: [],
|
|
1041
|
+
mayExecuteExternalProcess: false,
|
|
1042
|
+
description: "Binds a task to the current Git worktree identity; rebinding is not implicit.",
|
|
1043
|
+
}),
|
|
1044
|
+
"workspace-status": Object.freeze({
|
|
1045
|
+
name: "workspace-status",
|
|
1046
|
+
category: "workspace",
|
|
1047
|
+
mutation: "READ_ONLY",
|
|
1048
|
+
options: Object.freeze({
|
|
1049
|
+
...CLI_COMMON_OPTIONS,
|
|
1050
|
+
...CLI_TASK_OPTION,
|
|
1051
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit workspace status as JSON" }),
|
|
1052
|
+
}),
|
|
1053
|
+
writes: [],
|
|
1054
|
+
removes: [],
|
|
1055
|
+
mayExecuteExternalProcess: true,
|
|
1056
|
+
description: "Reports whether the current Git worktree matches an optional task binding.",
|
|
1057
|
+
}),
|
|
1058
|
+
"handoff-create": Object.freeze({
|
|
1059
|
+
name: "handoff-create",
|
|
1060
|
+
category: "continuity",
|
|
1061
|
+
mutation: "MUTATING",
|
|
1062
|
+
options: Object.freeze({
|
|
1063
|
+
...CLI_COMMON_OPTIONS,
|
|
1064
|
+
...CLI_TASK_OPTION,
|
|
1065
|
+
"--recipient": Object.freeze({ targetKey: "recipientHint", parseType: "string", takesValue: true, valueName: "hint", missingValueMessage: "--recipient requires a hint", description: "non-authoritative recipient hint" }),
|
|
1066
|
+
"--note": Object.freeze({ targetKey: "handoffNote", parseType: "string", takesValue: true, valueName: "text", missingValueMessage: "--note requires text", description: "non-authoritative handoff note" }),
|
|
1067
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit handoff as JSON" }),
|
|
1068
|
+
}),
|
|
1069
|
+
writes: [".forgeloop/task-state/<taskKey>/handoffs/handoff-<id>.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
|
|
1070
|
+
removes: [],
|
|
1071
|
+
mayExecuteExternalProcess: false,
|
|
1072
|
+
description: "Creates an immutable protocol-derived handoff snapshot without delegation or review side effects.",
|
|
1073
|
+
}),
|
|
1074
|
+
"handoff-list": Object.freeze({
|
|
1075
|
+
name: "handoff-list",
|
|
1076
|
+
category: "continuity",
|
|
1077
|
+
mutation: "READ_ONLY",
|
|
1078
|
+
options: Object.freeze({
|
|
1079
|
+
...CLI_COMMON_OPTIONS,
|
|
1080
|
+
...CLI_TASK_OPTION,
|
|
1081
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit handoff list as JSON" }),
|
|
1082
|
+
}),
|
|
1083
|
+
writes: [],
|
|
1084
|
+
removes: [],
|
|
1085
|
+
mayExecuteExternalProcess: false,
|
|
1086
|
+
description: "Lists immutable handoff snapshots for a task.",
|
|
1087
|
+
}),
|
|
1088
|
+
"handoff-show": Object.freeze({
|
|
1089
|
+
name: "handoff-show",
|
|
1090
|
+
category: "continuity",
|
|
1091
|
+
mutation: "READ_ONLY",
|
|
1092
|
+
options: Object.freeze({
|
|
1093
|
+
...CLI_COMMON_OPTIONS,
|
|
1094
|
+
...CLI_TASK_OPTION,
|
|
1095
|
+
"--id": Object.freeze({ targetKey: "handoffId", parseType: "string", takesValue: true, valueName: "id", missingValueMessage: "--id requires a handoff ID", description: "handoff identifier" }),
|
|
1096
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit handoff as JSON" }),
|
|
1097
|
+
}),
|
|
1098
|
+
writes: [],
|
|
1099
|
+
removes: [],
|
|
1100
|
+
mayExecuteExternalProcess: false,
|
|
1101
|
+
description: "Reads and verifies one immutable handoff snapshot.",
|
|
1102
|
+
}),
|
|
1103
|
+
"responsibility-set": Object.freeze({
|
|
1104
|
+
name: "responsibility-set",
|
|
1105
|
+
category: "scope",
|
|
1106
|
+
mutation: "MUTATING",
|
|
1107
|
+
options: Object.freeze({
|
|
1108
|
+
...CLI_COMMON_OPTIONS,
|
|
1109
|
+
...CLI_TASK_OPTION,
|
|
1110
|
+
"--label": Object.freeze({ targetKey: "responsibilityLabel", parseType: "string", takesValue: true, valueName: "label", missingValueMessage: "--label requires a value", description: "descriptive pass label; not an identity or role" }),
|
|
1111
|
+
"--allowed-path": Object.freeze({ targetKey: "responsibilityAllowedPaths", parseType: "string", takesValue: true, repeatable: true, valueName: "path", missingValueMessage: "--allowed-path requires a path", description: "path prefix allowed during this pass" }),
|
|
1112
|
+
"--read-only-path": Object.freeze({ targetKey: "responsibilityReadOnlyPaths", parseType: "string", takesValue: true, repeatable: true, valueName: "path", missingValueMessage: "--read-only-path requires a path", description: "path prefix that must not change during this pass" }),
|
|
1113
|
+
"--required-check": Object.freeze({ targetKey: "responsibilityRequiredChecks", parseType: "string", takesValue: true, repeatable: true, valueName: "id", missingValueMessage: "--required-check requires an ID", description: "current-cycle check required by this pass" }),
|
|
1114
|
+
"--freeze-contract": Object.freeze({ targetKey: "responsibilityFreezeContract", parseType: "boolean", takesValue: false, description: "freeze the current contract fingerprint" }),
|
|
1115
|
+
"--freeze-route": Object.freeze({ targetKey: "responsibilityFreezeRoute", parseType: "boolean", takesValue: false, description: "freeze the current route fingerprint" }),
|
|
1116
|
+
"--freeze-claims": Object.freeze({ targetKey: "responsibilityFreezeClaims", parseType: "boolean", takesValue: false, description: "freeze the current effective claims fingerprint" }),
|
|
1117
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit responsibility result as JSON" }),
|
|
1118
|
+
}),
|
|
1119
|
+
writes: [".forgeloop/task-state/<taskKey>/responsibility.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
|
|
1120
|
+
removes: [],
|
|
1121
|
+
mayExecuteExternalProcess: false,
|
|
1122
|
+
description: "Creates immutable, mechanically verifiable constraints for a task pass.",
|
|
1123
|
+
}),
|
|
1124
|
+
"responsibility-status": Object.freeze({
|
|
1125
|
+
name: "responsibility-status",
|
|
1126
|
+
category: "scope",
|
|
1127
|
+
mutation: "READ_ONLY",
|
|
1128
|
+
options: Object.freeze({
|
|
1129
|
+
...CLI_COMMON_OPTIONS,
|
|
1130
|
+
...CLI_TASK_OPTION,
|
|
1131
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit responsibility status as JSON" }),
|
|
1132
|
+
}),
|
|
1133
|
+
writes: [],
|
|
1134
|
+
removes: [],
|
|
1135
|
+
mayExecuteExternalProcess: true,
|
|
1136
|
+
description: "Validates active responsibility constraints against current paths, inputs, and checks.",
|
|
1137
|
+
}),
|
|
1138
|
+
"verify-scope": Object.freeze({
|
|
1139
|
+
name: "verify-scope",
|
|
1140
|
+
category: "verification",
|
|
1141
|
+
mutation: "MUTATING",
|
|
1142
|
+
options: Object.freeze({
|
|
1143
|
+
...CLI_COMMON_OPTIONS,
|
|
1144
|
+
...CLI_TASK_OPTION,
|
|
1145
|
+
"--mode": Object.freeze({ targetKey: "verificationScopeMode", parseType: "string", takesValue: true, valueName: "mode", missingValueMessage: "--mode requires AUTO, CHANGED, CLAIMED, or FULL", description: "requested deterministic verification boundary (default: AUTO)" }),
|
|
1146
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit verification scope as JSON" }),
|
|
1147
|
+
}),
|
|
1148
|
+
writes: [".forgeloop/task-state/<taskKey>/verification-scope.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
|
|
1149
|
+
removes: [],
|
|
1150
|
+
mayExecuteExternalProcess: true,
|
|
1151
|
+
description: "Resolves a provable changed, claimed, full, or unresolved verification boundary without launching checks.",
|
|
1152
|
+
}),
|
|
1153
|
+
"attestation-create": Object.freeze({
|
|
1154
|
+
name: "attestation-create",
|
|
1155
|
+
category: "attestation",
|
|
1156
|
+
mutation: "MUTATING",
|
|
1157
|
+
options: Object.freeze({
|
|
1158
|
+
...CLI_COMMON_OPTIONS,
|
|
1159
|
+
...CLI_TASK_OPTION,
|
|
1160
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit attestation statement result as JSON" }),
|
|
1161
|
+
}),
|
|
1162
|
+
writes: [".forgeloop/task-state/<taskKey>/attestations/statement.json", ".forgeloop/task-state/<taskKey>/events.ndjson"],
|
|
1163
|
+
removes: [],
|
|
1164
|
+
mayExecuteExternalProcess: false,
|
|
1165
|
+
description: "Builds a deterministic in-toto ForgeLoop statement from a valid completed task manifest.",
|
|
1166
|
+
}),
|
|
1167
|
+
"attestation-verify": Object.freeze({
|
|
1168
|
+
name: "attestation-verify",
|
|
1169
|
+
category: "attestation",
|
|
1170
|
+
mutation: "READ_ONLY",
|
|
1171
|
+
options: Object.freeze({
|
|
1172
|
+
...CLI_COMMON_OPTIONS,
|
|
1173
|
+
...CLI_TASK_OPTION,
|
|
1174
|
+
"--ref": Object.freeze({ targetKey: "attestationRef", parseType: "string", takesValue: true, valueName: "revision", missingValueMessage: "--ref requires a revision", description: "target revision to compare with the manifest" }),
|
|
1175
|
+
"--bundle": Object.freeze({ targetKey: "attestationBundle", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--bundle requires a path", description: "optional external signature bundle" }),
|
|
1176
|
+
"--identity": Object.freeze({ targetKey: "attestationIdentity", parseType: "string", takesValue: true, valueName: "identity", missingValueMessage: "--identity requires an identity", description: "optional exact signer identity policy" }),
|
|
1177
|
+
"--issuer": Object.freeze({ targetKey: "attestationIssuer", parseType: "string", takesValue: true, valueName: "issuer", missingValueMessage: "--issuer requires an issuer", description: "optional exact signer issuer policy" }),
|
|
1178
|
+
"--revision-provider": Object.freeze({ targetKey: "revisionProvider", parseType: "string", takesValue: true, valueName: "provider", missingValueMessage: "--revision-provider requires a provider", description: "revision provider (default: git)" }),
|
|
1179
|
+
"--signing-provider": Object.freeze({ targetKey: "signingProvider", parseType: "string", takesValue: true, valueName: "provider", missingValueMessage: "--signing-provider requires a provider", description: "signing provider (default: none)" }),
|
|
1180
|
+
"--trusted-root": Object.freeze({ targetKey: "trustedRoot", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--trusted-root requires a path", description: "trusted signing root path" }),
|
|
1181
|
+
"--require-signature": Object.freeze({ targetKey: "requireSignature", parseType: "boolean", takesValue: false, description: "require a valid external signature" }),
|
|
1182
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit verification result as JSON" }),
|
|
1183
|
+
}),
|
|
1184
|
+
writes: [],
|
|
1185
|
+
removes: [],
|
|
1186
|
+
mayExecuteExternalProcess: true,
|
|
1187
|
+
description: "Verifies a task attestation and current revision without mutating task state.",
|
|
1188
|
+
}),
|
|
1189
|
+
"attestation-status": Object.freeze({
|
|
1190
|
+
name: "attestation-status",
|
|
1191
|
+
category: "attestation",
|
|
1192
|
+
mutation: "READ_ONLY",
|
|
1193
|
+
options: Object.freeze({
|
|
1194
|
+
...CLI_COMMON_OPTIONS,
|
|
1195
|
+
...CLI_TASK_OPTION,
|
|
1196
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit attestation status as JSON" }),
|
|
1197
|
+
}),
|
|
1198
|
+
writes: [],
|
|
1199
|
+
removes: [],
|
|
1200
|
+
mayExecuteExternalProcess: true,
|
|
1201
|
+
description: "Reports local ForgeLoop attestation status and trust level without network access.",
|
|
1202
|
+
}),
|
|
1203
|
+
"attestation-verify-range": Object.freeze({
|
|
1204
|
+
name: "attestation-verify-range",
|
|
1205
|
+
category: "attestation",
|
|
1206
|
+
mutation: "READ_ONLY",
|
|
1207
|
+
options: Object.freeze({
|
|
1208
|
+
...CLI_COMMON_OPTIONS,
|
|
1209
|
+
"--revision-provider": Object.freeze({ targetKey: "revisionProvider", parseType: "string", takesValue: true, valueName: "provider", missingValueMessage: "--revision-provider requires a provider", description: "revision provider (default: git)" }),
|
|
1210
|
+
"--base": Object.freeze({ targetKey: "baseRevision", parseType: "string", takesValue: true, valueName: "revision", missingValueMessage: "--base requires a revision", description: "opaque base revision" }),
|
|
1211
|
+
"--head": Object.freeze({ targetKey: "headRevision", parseType: "string", takesValue: true, valueName: "revision", missingValueMessage: "--head requires a revision", description: "opaque head revision" }),
|
|
1212
|
+
"--require-complete-coverage": Object.freeze({ targetKey: "requireCompleteCoverage", parseType: "boolean", takesValue: false, description: "fail when any changed source path lacks a valid attestation" }),
|
|
1213
|
+
"--require-signature": Object.freeze({ targetKey: "requireSignature", parseType: "boolean", takesValue: false, description: "require a valid external signature for covered attestations" }),
|
|
1214
|
+
"--signing-provider": Object.freeze({ targetKey: "signingProvider", parseType: "string", takesValue: true, valueName: "provider", missingValueMessage: "--signing-provider requires a provider", description: "signing provider (default: none)" }),
|
|
1215
|
+
"--identity": Object.freeze({ targetKey: "attestationIdentity", parseType: "string", takesValue: true, valueName: "identity", missingValueMessage: "--identity requires an identity", description: "optional exact signer identity policy" }),
|
|
1216
|
+
"--issuer": Object.freeze({ targetKey: "attestationIssuer", parseType: "string", takesValue: true, valueName: "issuer", missingValueMessage: "--issuer requires an issuer", description: "optional exact signer issuer policy" }),
|
|
1217
|
+
"--trusted-root": Object.freeze({ targetKey: "trustedRoot", parseType: "string", takesValue: true, valueName: "path", missingValueMessage: "--trusted-root requires a path", description: "trusted signing root path" }),
|
|
1218
|
+
"--json": Object.freeze({ targetKey: "json", parseType: "boolean", takesValue: false, description: "emit range verification result as JSON" }),
|
|
1219
|
+
}),
|
|
1220
|
+
writes: [],
|
|
1221
|
+
removes: [],
|
|
1222
|
+
mayExecuteExternalProcess: true,
|
|
1223
|
+
description: "Verifies complete attestation coverage for an opaque revision range through a provider-neutral contract.",
|
|
1224
|
+
}),
|
|
1029
1225
|
});
|
|
1030
1226
|
|
|
1031
1227
|
/**
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { canonicalFingerprint, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
5
|
+
import { assertSchema, readSchema } from "./schema-validation.js";
|
|
6
|
+
import { assertSecretFree } from "./receipt.js";
|
|
7
|
+
import { getPackageRoot } from "./templates.js";
|
|
8
|
+
import { readContract } from "./contract.js";
|
|
9
|
+
import { readPersistedRoute } from "./route-artifact.js";
|
|
10
|
+
import { readWorkState } from "./work-state.js";
|
|
11
|
+
import { validateEventLedger } from "./events.js";
|
|
12
|
+
import {
|
|
13
|
+
taskArtifactPath,
|
|
14
|
+
taskCodeManifestPath,
|
|
15
|
+
taskCodeManifestHistoryPath,
|
|
16
|
+
} from "./task-paths.js";
|
|
17
|
+
import { ensureWithin, fileExists } from "./filesystem.js";
|
|
18
|
+
import { resolveRevisionProvider } from "./revision/provider.js";
|
|
19
|
+
import { REVISION_PROVIDERS } from "./revision/registry.js";
|
|
20
|
+
|
|
21
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
22
|
+
const OPERATIONS = new Set(["ADDED", "MODIFIED", "DELETED", "RENAMED", "COPIED", "TYPE_CHANGED"]);
|
|
23
|
+
const KINDS = new Set(["FILE", "SYMLINK", "GITLINK", "DELETED"]);
|
|
24
|
+
|
|
25
|
+
function manifestError(code, message, artifacts = []) {
|
|
26
|
+
const error = new Error(message);
|
|
27
|
+
error.name = "CodeManifestError";
|
|
28
|
+
error.code = code;
|
|
29
|
+
error.artifacts = artifacts;
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function digestBytes(bytes) {
|
|
34
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function normalizedPath(value, label = "path") {
|
|
38
|
+
if (typeof value !== "string" || !value || value.startsWith("/") || value === "." || value === ".." || value.startsWith("../")) {
|
|
39
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `${label} must be a safe relative path`);
|
|
40
|
+
}
|
|
41
|
+
if (/[\u0000-\u001f\u007f]/u.test(value)) {
|
|
42
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `${label} contains a control character`);
|
|
43
|
+
}
|
|
44
|
+
if (value.includes("\\")) {
|
|
45
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `${label} must use forward slashes`);
|
|
46
|
+
}
|
|
47
|
+
const portable = value;
|
|
48
|
+
const normalized = path.posix.normalize(portable).replace(/^\.\//u, "");
|
|
49
|
+
if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.startsWith(".forgeloop/")) {
|
|
50
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `${label} is unsafe or reserved: ${value}`);
|
|
51
|
+
}
|
|
52
|
+
if (normalized !== portable) {
|
|
53
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `${label} must use a normalized relative path: ${value}`);
|
|
54
|
+
}
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function codeManifestContentDigest(entries) {
|
|
59
|
+
return canonicalFingerprint(entries.map((entry) => ({
|
|
60
|
+
path: entry.path,
|
|
61
|
+
sourcePath: entry.sourcePath ?? null,
|
|
62
|
+
operation: entry.operation,
|
|
63
|
+
kind: entry.kind,
|
|
64
|
+
sha256: entry.sha256 ?? null,
|
|
65
|
+
providerContentId: entry.providerContentId ?? null,
|
|
66
|
+
providerMetadata: entry.providerMetadata ?? {},
|
|
67
|
+
})));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function canonicalEntries(entries) {
|
|
71
|
+
if (!Array.isArray(entries)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", "Manifest entries must be an array");
|
|
72
|
+
const normalized = entries.map((entry, index) => {
|
|
73
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Manifest entry ${index} must be an object`);
|
|
74
|
+
const item = {
|
|
75
|
+
path: normalizedPath(entry.path, `entries[${index}].path`),
|
|
76
|
+
...(entry.sourcePath !== undefined ? { sourcePath: normalizedPath(entry.sourcePath, `entries[${index}].sourcePath`) } : {}),
|
|
77
|
+
operation: entry.operation,
|
|
78
|
+
kind: entry.kind,
|
|
79
|
+
...(entry.sha256 !== undefined ? { sha256: entry.sha256 } : {}),
|
|
80
|
+
...(entry.providerContentId !== undefined ? { providerContentId: entry.providerContentId } : {}),
|
|
81
|
+
...(entry.providerMetadata !== undefined ? { providerMetadata: entry.providerMetadata } : {}),
|
|
82
|
+
};
|
|
83
|
+
if (!OPERATIONS.has(item.operation) || !KINDS.has(item.kind)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Manifest entry ${index} has an unsupported operation or kind`);
|
|
84
|
+
if (item.operation === "DELETED" && item.kind !== "DELETED") throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Deleted entry ${item.path} must use kind DELETED`);
|
|
85
|
+
if (item.kind === "DELETED" && item.operation !== "DELETED") throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Deleted entry ${item.path} must use operation DELETED`);
|
|
86
|
+
if (item.kind === "DELETED" && item.sha256 !== undefined) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Deleted entry ${item.path} cannot have sha256`);
|
|
87
|
+
if (["FILE", "SYMLINK"].includes(item.kind) && !SHA256.test(item.sha256 ?? "")) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Entry ${item.path} requires a lowercase SHA-256 content digest`);
|
|
88
|
+
if (item.kind === "GITLINK" && (typeof item.providerContentId !== "string" || !item.providerContentId)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Gitlink entry ${item.path} requires providerContentId`);
|
|
89
|
+
if (item.providerMetadata !== undefined && (!item.providerMetadata || typeof item.providerMetadata !== "object" || Array.isArray(item.providerMetadata))) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Entry ${item.path} providerMetadata must be an object`);
|
|
90
|
+
if (item.operation === "RENAMED" && !item.sourcePath) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Renamed entry ${item.path} requires sourcePath`);
|
|
91
|
+
return item;
|
|
92
|
+
});
|
|
93
|
+
const paths = normalized.map((entry) => entry.path);
|
|
94
|
+
if (new Set(paths).size !== paths.length) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", "Manifest entries must not contain duplicate paths");
|
|
95
|
+
const sorted = [...normalized].sort((left, right) => left.path.localeCompare(right.path));
|
|
96
|
+
if (JSON.stringify(sorted) !== JSON.stringify(normalized)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", "Manifest entries must be sorted by normalized path");
|
|
97
|
+
return normalized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function validateCodeManifest(manifest, packageRoot = getPackageRoot()) {
|
|
101
|
+
try {
|
|
102
|
+
assertSchema(manifest, await readSchema("code-manifest", packageRoot), "code manifest");
|
|
103
|
+
const entries = canonicalEntries(manifest.entries);
|
|
104
|
+
if (manifest.contentDigest !== codeManifestContentDigest(entries)) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", "Manifest contentDigest does not match its entries");
|
|
105
|
+
assertSecretFree(manifest);
|
|
106
|
+
return { ...manifest, entries };
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code === "E_ATTESTATION_MANIFEST_INVALID") throw error;
|
|
109
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", error.message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function completionCheckpoint(target, packageRoot, taskId, override = null) {
|
|
114
|
+
if (override) {
|
|
115
|
+
if (!Number.isInteger(override.seq) || override.seq < 1 || !SHA256.test(override.hash)) {
|
|
116
|
+
throw manifestError("E_ATTESTATION_LEDGER_MISMATCH", "The candidate completion checkpoint is invalid");
|
|
117
|
+
}
|
|
118
|
+
return { seq: override.seq, hash: override.hash, ledger: null };
|
|
119
|
+
}
|
|
120
|
+
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
121
|
+
const completion = ledger.events.findLast((event) => event.taskId === taskId && event.event === "COMPLETION_VALIDATED");
|
|
122
|
+
if (!ledger.valid || !completion) throw manifestError("E_ATTESTATION_LEDGER_MISMATCH", "A valid COMPLETION_VALIDATED ledger checkpoint is required for a code manifest");
|
|
123
|
+
return { seq: completion.seq, hash: completion.hash, ledger };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function createCodeManifest({
|
|
127
|
+
target,
|
|
128
|
+
packageRoot = getPackageRoot(),
|
|
129
|
+
taskId,
|
|
130
|
+
revisionProvider = null,
|
|
131
|
+
baseRevision = null,
|
|
132
|
+
headRevision = "WORKTREE",
|
|
133
|
+
captureMode = "WORKTREE",
|
|
134
|
+
candidateState = null,
|
|
135
|
+
candidateReceipt = null,
|
|
136
|
+
candidateCompletionCheckpoint = null,
|
|
137
|
+
} = {}) {
|
|
138
|
+
let provider = revisionProvider;
|
|
139
|
+
if (!provider || typeof provider === "string") {
|
|
140
|
+
provider = await resolveRevisionProvider({ target, providerName: provider || "git", registry: REVISION_PROVIDERS });
|
|
141
|
+
}
|
|
142
|
+
const [contract, route, state, receipt, checkpoint] = await Promise.all([
|
|
143
|
+
readContract(target, packageRoot, { taskId }),
|
|
144
|
+
readPersistedRoute(target, packageRoot, { taskId }),
|
|
145
|
+
candidateState ?? readWorkState(target, { packageRoot, taskId }),
|
|
146
|
+
candidateReceipt
|
|
147
|
+
? { value: candidateReceipt, fingerprint: canonicalFingerprint(candidateReceipt) }
|
|
148
|
+
: readJsonArtifact(target, taskArtifactPath(taskId, "receipt"), "execution-receipt", packageRoot),
|
|
149
|
+
completionCheckpoint(target, packageRoot, taskId, candidateCompletionCheckpoint),
|
|
150
|
+
]);
|
|
151
|
+
if (!state || state.phase !== "COMPLETE") throw manifestError("E_ATTESTATION_MANIFEST_STALE", "Code manifests can only be captured for a COMPLETE task");
|
|
152
|
+
const observedRevision = headRevision === "WORKTREE"
|
|
153
|
+
? (await provider.getCurrentRevision(target)) ?? "WORKTREE"
|
|
154
|
+
: headRevision;
|
|
155
|
+
let entries;
|
|
156
|
+
try {
|
|
157
|
+
entries = await provider.getChangedEntries({ target, baseRevision, headRevision, paths: null });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw manifestError(error.code ?? "E_REVISION_PROVIDER_INVALID", error.message);
|
|
160
|
+
}
|
|
161
|
+
const manifestEntries = [];
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const pathValue = normalizedPath(entry.path);
|
|
164
|
+
if (pathValue.startsWith(".forgeloop/")) throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Protocol metadata cannot enter a code manifest: ${pathValue}`);
|
|
165
|
+
const kind = entry.kind ?? (entry.operation === "DELETED" ? "DELETED" : "FILE");
|
|
166
|
+
const bytes = entry.bytes ?? (kind === "DELETED" || kind === "GITLINK" ? null : await provider.readContent({ target, revision: headRevision, path: pathValue }));
|
|
167
|
+
manifestEntries.push({
|
|
168
|
+
path: pathValue,
|
|
169
|
+
...(entry.sourcePath ? { sourcePath: normalizedPath(entry.sourcePath, "sourcePath") } : {}),
|
|
170
|
+
operation: entry.operation,
|
|
171
|
+
kind,
|
|
172
|
+
...(bytes !== null && ["FILE", "SYMLINK"].includes(kind) ? { sha256: digestBytes(bytes) } : {}),
|
|
173
|
+
...(entry.providerContentId ? { providerContentId: entry.providerContentId } : {}),
|
|
174
|
+
providerMetadata: entry.providerMetadata ?? {},
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
manifestEntries.sort((left, right) => left.path.localeCompare(right.path));
|
|
178
|
+
const providerMetadata = typeof provider.getProviderMetadata === "function"
|
|
179
|
+
? await provider.getProviderMetadata(target)
|
|
180
|
+
: {};
|
|
181
|
+
const manifest = await validateCodeManifest({
|
|
182
|
+
schemaVersion: 1,
|
|
183
|
+
protocolVersion: 1,
|
|
184
|
+
taskId,
|
|
185
|
+
verificationCycle: state.verificationCycle ?? 1,
|
|
186
|
+
capture: {
|
|
187
|
+
mode: captureMode,
|
|
188
|
+
revisionProvider: provider.name ?? "unknown",
|
|
189
|
+
baseRevision,
|
|
190
|
+
observedRevision,
|
|
191
|
+
providerMetadata,
|
|
192
|
+
},
|
|
193
|
+
bindings: {
|
|
194
|
+
contractFingerprint: contract.fingerprint,
|
|
195
|
+
routeFingerprint: route.fingerprint ?? null,
|
|
196
|
+
stateFingerprint: canonicalFingerprint(state),
|
|
197
|
+
receiptFingerprint: receipt.fingerprint,
|
|
198
|
+
ledgerSeq: checkpoint.seq,
|
|
199
|
+
ledgerHash: checkpoint.hash,
|
|
200
|
+
},
|
|
201
|
+
entries: manifestEntries,
|
|
202
|
+
contentDigest: codeManifestContentDigest(manifestEntries),
|
|
203
|
+
}, packageRoot);
|
|
204
|
+
return { manifest, fingerprint: canonicalFingerprint(manifest), provider, checkpoint };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function readCodeManifest({ target, packageRoot = getPackageRoot(), taskId } = {}) {
|
|
208
|
+
const relativePath = taskCodeManifestPath(taskId);
|
|
209
|
+
try {
|
|
210
|
+
const artifact = await readJsonArtifact(target, relativePath, "code-manifest", packageRoot);
|
|
211
|
+
const value = await validateCodeManifest(artifact.value, packageRoot);
|
|
212
|
+
return { ...artifact, value };
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (error.code === "ARTIFACT_MISSING") throw manifestError("E_ATTESTATION_MANIFEST_MISSING", `Code manifest is missing: ${relativePath}`, [relativePath]);
|
|
215
|
+
if (error.code?.startsWith("E_ATTESTATION_")) throw error;
|
|
216
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", error.message, [relativePath]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function writeCodeManifest({ target, packageRoot = getPackageRoot(), taskId, manifest } = {}) {
|
|
221
|
+
const value = await validateCodeManifest(manifest, packageRoot);
|
|
222
|
+
const relativePath = taskCodeManifestPath(taskId);
|
|
223
|
+
if (await fileExists(ensureWithin(target, relativePath))) {
|
|
224
|
+
let previous;
|
|
225
|
+
try {
|
|
226
|
+
previous = await readCodeManifest({ target, packageRoot, taskId });
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", `Existing code manifest cannot be versioned safely: ${error.message}`, [relativePath]);
|
|
229
|
+
}
|
|
230
|
+
const previousCycle = previous.value.verificationCycle;
|
|
231
|
+
if (value.verificationCycle <= previousCycle) {
|
|
232
|
+
throw manifestError("E_ATTESTATION_MANIFEST_INVALID", "Code manifest is immutable within a verification cycle", [relativePath]);
|
|
233
|
+
}
|
|
234
|
+
const historyPath = taskCodeManifestHistoryPath(taskId, previousCycle);
|
|
235
|
+
if (!(await fileExists(ensureWithin(target, historyPath)))) {
|
|
236
|
+
await writeJsonArtifact(target, historyPath, previous.value, "code-manifest", packageRoot, { taskId, operation: "code-manifest-history" });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return writeJsonArtifact(target, relativePath, value, "code-manifest", packageRoot, { taskId, operation: "code-manifest-capture" });
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function validateCodeManifestBindings({ target, packageRoot = getPackageRoot(), taskId, manifest, revisionProvider = null } = {}) {
|
|
243
|
+
const value = await validateCodeManifest(manifest, packageRoot);
|
|
244
|
+
const [contract, route, state, receipt, checkpoint] = await Promise.all([
|
|
245
|
+
readContract(target, packageRoot, { taskId }),
|
|
246
|
+
readPersistedRoute(target, packageRoot, { taskId }),
|
|
247
|
+
readWorkState(target, { packageRoot, taskId }),
|
|
248
|
+
readJsonArtifact(target, taskArtifactPath(taskId, "receipt"), "execution-receipt", packageRoot),
|
|
249
|
+
completionCheckpoint(target, packageRoot, taskId),
|
|
250
|
+
]);
|
|
251
|
+
const mismatches = [];
|
|
252
|
+
if (value.taskId !== taskId) mismatches.push(["taskId", "E_ATTESTATION_SUBJECT_MISMATCH"]);
|
|
253
|
+
if (value.verificationCycle !== (state.verificationCycle ?? 1)) mismatches.push(["verificationCycle", "E_ATTESTATION_SCOPE_MISMATCH"]);
|
|
254
|
+
if (value.bindings.contractFingerprint !== contract.fingerprint) mismatches.push(["contractFingerprint", "E_ATTESTATION_CONTRACT_MISMATCH"]);
|
|
255
|
+
if (value.bindings.routeFingerprint !== (route.fingerprint ?? null)) mismatches.push(["routeFingerprint", "E_ATTESTATION_ROUTE_MISMATCH"]);
|
|
256
|
+
if (value.bindings.stateFingerprint !== canonicalFingerprint(state)) mismatches.push(["stateFingerprint", "E_ATTESTATION_STATE_MISMATCH"]);
|
|
257
|
+
if (value.bindings.receiptFingerprint !== receipt.fingerprint) mismatches.push(["receiptFingerprint", "E_ATTESTATION_RECEIPT_MISMATCH"]);
|
|
258
|
+
if (value.bindings.ledgerSeq !== checkpoint.seq || value.bindings.ledgerHash !== checkpoint.hash) mismatches.push(["ledger", "E_ATTESTATION_LEDGER_MISMATCH"]);
|
|
259
|
+
if (mismatches.length > 0) {
|
|
260
|
+
const names = mismatches.map(([name]) => name).join(", ");
|
|
261
|
+
throw manifestError(mismatches[0][1], `Code manifest bindings are stale: ${names}`);
|
|
262
|
+
}
|
|
263
|
+
if (revisionProvider) {
|
|
264
|
+
const provider = typeof revisionProvider === "string"
|
|
265
|
+
? await resolveRevisionProvider({ target, providerName: revisionProvider, registry: REVISION_PROVIDERS })
|
|
266
|
+
: revisionProvider;
|
|
267
|
+
const manifestPaths = new Set(value.entries.flatMap((entry) => [entry.path, entry.sourcePath].filter(Boolean)));
|
|
268
|
+
const currentEntries = await provider.getChangedEntries({
|
|
269
|
+
target,
|
|
270
|
+
baseRevision: value.capture.baseRevision,
|
|
271
|
+
headRevision: value.capture.mode === "WORKTREE" ? "WORKTREE" : value.capture.observedRevision,
|
|
272
|
+
});
|
|
273
|
+
const sourcePaths = (entries) => entries
|
|
274
|
+
.flatMap((entry) => [entry.path, entry.sourcePath].filter(Boolean))
|
|
275
|
+
.map((item) => normalizedPath(item))
|
|
276
|
+
.filter((item) => item !== ".forgeloop" && !item.startsWith(".forgeloop/"));
|
|
277
|
+
let currentPaths = new Set(sourcePaths(currentEntries));
|
|
278
|
+
const manifestPathsPresent = [...manifestPaths].every((item) => currentPaths.has(item));
|
|
279
|
+
if (!manifestPathsPresent && value.capture.mode === "WORKTREE" && value.capture.baseRevision) {
|
|
280
|
+
const observedRevision = typeof provider.getCurrentRevision === "function"
|
|
281
|
+
? await provider.getCurrentRevision(target).catch(() => null)
|
|
282
|
+
: null;
|
|
283
|
+
if (observedRevision) {
|
|
284
|
+
const committedEntries = await provider.getChangedEntries({ target, baseRevision: value.capture.baseRevision, headRevision: observedRevision });
|
|
285
|
+
currentPaths = new Set(sourcePaths(committedEntries));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (![...manifestPaths].every((item) => currentPaths.has(item))) {
|
|
289
|
+
throw manifestError("E_ATTESTATION_CONTENT_MISMATCH", "Current changed source paths differ from the code manifest");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return { valid: true, manifest: value, fingerprint: canonicalFingerprint(value), checkpoint };
|
|
293
|
+
}
|
|
@@ -63,6 +63,19 @@ import { runTaskResume } from "../commands/task-resume.js";
|
|
|
63
63
|
import { runTaskRepairLegacyRecovery } from "../commands/task-repair-legacy-recovery.js";
|
|
64
64
|
import { runTaskLockStatus } from "../commands/task-lock-status.js";
|
|
65
65
|
import { runProtocolInfo } from "../commands/protocol-info.js";
|
|
66
|
+
import { runWorkspaceBind } from "../commands/workspace-bind.js";
|
|
67
|
+
import { runWorkspaceStatus } from "../commands/workspace-status.js";
|
|
68
|
+
import { runHandoffCreate } from "../commands/handoff-create.js";
|
|
69
|
+
import { runHandoffList } from "../commands/handoff-list.js";
|
|
70
|
+
import { runHandoffShow } from "../commands/handoff-show.js";
|
|
71
|
+
import { runResponsibilitySet } from "../commands/responsibility-set.js";
|
|
72
|
+
import { runResponsibilityStatus } from "../commands/responsibility-status.js";
|
|
73
|
+
import { runVerifyScope } from "../commands/verify-scope.js";
|
|
74
|
+
import { runAttestationCreate } from "../commands/attestation-create.js";
|
|
75
|
+
import { runAttestationVerify } from "../commands/attestation-verify.js";
|
|
76
|
+
import { runAttestationStatus } from "../commands/attestation-status.js";
|
|
77
|
+
import { runAttestationVerifyRange } from "../commands/attestation-verify-range.js";
|
|
78
|
+
import { exitCodeForAttestationResult } from "./exit-codes.js";
|
|
66
79
|
|
|
67
80
|
/**
|
|
68
81
|
* Canonical transport-neutral command executors.
|
|
@@ -171,12 +184,78 @@ export const COMMAND_EXECUTORS = {
|
|
|
171
184
|
argv: options.commandArgv,
|
|
172
185
|
details: options.checkDetails ?? undefined,
|
|
173
186
|
timeoutMs: options.timeoutMs ?? undefined,
|
|
187
|
+
scopeRef: options.scopeRef ?? undefined,
|
|
174
188
|
taskId: options.taskId,
|
|
175
189
|
authorityContext,
|
|
176
190
|
runtimeContext,
|
|
177
191
|
});
|
|
178
192
|
return { result, exitCode: result.check.status === "passed" ? 0 : 1 };
|
|
179
193
|
},
|
|
194
|
+
"workspace-bind": async ({ target, packageRoot, options }) => ({
|
|
195
|
+
result: await runWorkspaceBind({ target, packageRoot, taskId: options.taskId }),
|
|
196
|
+
exitCode: 0,
|
|
197
|
+
}),
|
|
198
|
+
"workspace-status": async ({ target, packageRoot, options }) => {
|
|
199
|
+
const result = await runWorkspaceStatus({ target, packageRoot, taskId: options.taskId });
|
|
200
|
+
return { result, exitCode: ["UNBOUND", "MATCH"].includes(result.status) ? 0 : 1 };
|
|
201
|
+
},
|
|
202
|
+
"handoff-create": async ({ target, packageRoot, options }) => ({
|
|
203
|
+
result: await runHandoffCreate({
|
|
204
|
+
target,
|
|
205
|
+
packageRoot,
|
|
206
|
+
taskId: options.taskId,
|
|
207
|
+
recipientHint: options.recipientHint,
|
|
208
|
+
handoffNote: options.handoffNote,
|
|
209
|
+
}),
|
|
210
|
+
exitCode: 0,
|
|
211
|
+
}),
|
|
212
|
+
"handoff-list": async ({ target, packageRoot, options }) => ({
|
|
213
|
+
result: await runHandoffList({ target, packageRoot, taskId: options.taskId }),
|
|
214
|
+
exitCode: 0,
|
|
215
|
+
}),
|
|
216
|
+
"handoff-show": async ({ target, packageRoot, options }) => ({
|
|
217
|
+
result: await runHandoffShow({ target, packageRoot, taskId: options.taskId, handoffId: options.handoffId }),
|
|
218
|
+
exitCode: 0,
|
|
219
|
+
}),
|
|
220
|
+
"responsibility-set": async ({ target, packageRoot, options }) => ({
|
|
221
|
+
result: await runResponsibilitySet({
|
|
222
|
+
target,
|
|
223
|
+
packageRoot,
|
|
224
|
+
taskId: options.taskId,
|
|
225
|
+
responsibilityLabel: options.responsibilityLabel,
|
|
226
|
+
responsibilityAllowedPaths: options.responsibilityAllowedPaths,
|
|
227
|
+
responsibilityReadOnlyPaths: options.responsibilityReadOnlyPaths,
|
|
228
|
+
responsibilityRequiredChecks: options.responsibilityRequiredChecks,
|
|
229
|
+
responsibilityFreezeContract: options.responsibilityFreezeContract,
|
|
230
|
+
responsibilityFreezeRoute: options.responsibilityFreezeRoute,
|
|
231
|
+
responsibilityFreezeClaims: options.responsibilityFreezeClaims,
|
|
232
|
+
}),
|
|
233
|
+
exitCode: 0,
|
|
234
|
+
}),
|
|
235
|
+
"responsibility-status": async ({ target, packageRoot, options }) => {
|
|
236
|
+
const result = await runResponsibilityStatus({ target, packageRoot, taskId: options.taskId });
|
|
237
|
+
return { result, exitCode: ["NOT_APPLICABLE", "VALID"].includes(result.status) ? 0 : 1 };
|
|
238
|
+
},
|
|
239
|
+
"verify-scope": async ({ target, packageRoot, options }) => ({
|
|
240
|
+
result: await runVerifyScope({ target, packageRoot, taskId: options.taskId, verificationScopeMode: options.verificationScopeMode }),
|
|
241
|
+
exitCode: 0,
|
|
242
|
+
}),
|
|
243
|
+
"attestation-create": async ({ target, packageRoot, options }) => ({
|
|
244
|
+
result: await runAttestationCreate({ target, packageRoot, taskId: options.taskId }),
|
|
245
|
+
exitCode: 0,
|
|
246
|
+
}),
|
|
247
|
+
"attestation-verify": async ({ target, packageRoot, options }) => {
|
|
248
|
+
const result = await runAttestationVerify({ target, packageRoot, taskId: options.taskId, ...options });
|
|
249
|
+
return { result, exitCode: exitCodeForAttestationResult(result) };
|
|
250
|
+
},
|
|
251
|
+
"attestation-status": async ({ target, packageRoot, options }) => {
|
|
252
|
+
const result = await runAttestationStatus({ target, packageRoot, taskId: options.taskId });
|
|
253
|
+
return { result, exitCode: exitCodeForAttestationResult(result) };
|
|
254
|
+
},
|
|
255
|
+
"attestation-verify-range": async ({ target, packageRoot, options }) => {
|
|
256
|
+
const result = await runAttestationVerifyRange({ target, packageRoot, ...options });
|
|
257
|
+
return { result, exitCode: exitCodeForAttestationResult(result) };
|
|
258
|
+
},
|
|
180
259
|
"run-action": async ({ target, packageRoot, options, authorityContext, runtimeContext }) => {
|
|
181
260
|
const result = await runAction({ target, packageRoot, taskId: options.taskId,
|
|
182
261
|
actionId: options.actionId, capability: options.actionCapability,
|