@kontourai/flow-agents 1.4.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/.github/CODEOWNERS +29 -0
  2. package/.github/actions/trust-verify/action.yml +145 -0
  3. package/.github/workflows/ci.yml +11 -4
  4. package/.github/workflows/kit-gates-demo.yml +2 -2
  5. package/.github/workflows/publish-npm.yml +10 -2
  6. package/.github/workflows/release-please.yml +1 -1
  7. package/.github/workflows/runtime-compat.yml +1 -1
  8. package/.github/workflows/trust-reconcile.yml +113 -0
  9. package/AGENTS.md +13 -0
  10. package/CHANGELOG.md +103 -0
  11. package/CONTRIBUTING.md +4 -4
  12. package/README.md +1 -0
  13. package/agents/tool-planner.json +1 -1
  14. package/build/src/cli/init.js +242 -20
  15. package/build/src/cli/validate-workflow-artifacts.js +19 -2
  16. package/build/src/cli/verify.d.ts +1 -0
  17. package/build/src/cli/verify.js +90 -0
  18. package/build/src/cli/workflow-sidecar.d.ts +316 -8
  19. package/build/src/cli/workflow-sidecar.js +1996 -91
  20. package/build/src/cli.js +2 -3
  21. package/build/src/lib/flow-resolver.d.ts +111 -0
  22. package/build/src/lib/flow-resolver.js +308 -0
  23. package/build/src/tools/build-universal-bundles.js +34 -22
  24. package/build/src/tools/generate-context-map.js +3 -16
  25. package/build/src/tools/validate-source-tree.d.ts +1 -1
  26. package/build/src/tools/validate-source-tree.js +42 -162
  27. package/context/contracts/artifact-contract.md +10 -0
  28. package/context/contracts/delivery-contract.md +1 -0
  29. package/context/contracts/review-contract.md +1 -0
  30. package/context/contracts/verification-contract.md +2 -0
  31. package/context/gate-awareness.md +39 -0
  32. package/context/scripts/hooks/stop-goal-fit.js +632 -70
  33. package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
  34. package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
  35. package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
  36. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
  37. package/docs/adr/0007-skill-audit.md +1 -1
  38. package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
  39. package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
  40. package/docs/adr/0011-mcp-posture.md +100 -0
  41. package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
  42. package/docs/adr/0013-context-lifecycle.md +151 -0
  43. package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
  44. package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
  45. package/docs/adr/0016-three-hard-boundary-model.md +71 -0
  46. package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
  47. package/docs/agent-system-guidebook.md +5 -12
  48. package/docs/context-map.md +4 -10
  49. package/docs/index.md +3 -2
  50. package/docs/integrations/framework-adapter.md +19 -6
  51. package/docs/integrations/index.md +2 -2
  52. package/docs/north-star.md +4 -4
  53. package/docs/operating-layers.md +3 -3
  54. package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
  55. package/docs/repository-structure.md +2 -2
  56. package/docs/skills-map.md +1 -0
  57. package/docs/spec/runtime-hook-surface.md +62 -9
  58. package/docs/standards-register.md +3 -3
  59. package/docs/survey-utterance-check.md +1 -1
  60. package/docs/trust-anchor-adoption.md +197 -0
  61. package/docs/verifiable-trust.md +95 -0
  62. package/docs/veritas-integration.md +2 -2
  63. package/docs/workflow-usage-guide.md +69 -0
  64. package/evals/acceptance/DEMO-false-completion.md +144 -0
  65. package/evals/acceptance/demo-cast.sh +92 -0
  66. package/evals/acceptance/demo-false-completion.sh +72 -0
  67. package/evals/acceptance/demo-real-evidence.sh +104 -0
  68. package/evals/acceptance/demo.tape +29 -0
  69. package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
  70. package/evals/acceptance/prove-capture-teeth.sh +114 -0
  71. package/evals/acceptance/prove-teeth.sh +105 -0
  72. package/evals/ci/antigaming-suite.sh +55 -0
  73. package/evals/ci/run-baseline.sh +2 -0
  74. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
  75. package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
  76. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
  77. package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
  78. package/evals/integration/test_builder_step_producers.sh +379 -0
  79. package/evals/integration/test_bundle_install.sh +35 -71
  80. package/evals/integration/test_bundle_lifecycle.sh +39 -2
  81. package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
  82. package/evals/integration/test_checkpoint_signing.sh +489 -0
  83. package/evals/integration/test_claim_lookup.sh +352 -0
  84. package/evals/integration/test_command_log_fork_classification.sh +134 -0
  85. package/evals/integration/test_command_log_integrity.sh +275 -0
  86. package/evals/integration/test_context_map.sh +0 -2
  87. package/evals/integration/test_dual_emit_flow_step.sh +278 -0
  88. package/evals/integration/test_enforcer_expects_driven.sh +281 -0
  89. package/evals/integration/test_evidence_capture_hook.sh +185 -0
  90. package/evals/integration/test_flow_kit_repository.sh +2 -0
  91. package/evals/integration/test_flowdef_session_activation.sh +273 -0
  92. package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
  93. package/evals/integration/test_gate_bypass_chain.sh +448 -0
  94. package/evals/integration/test_gate_lockdown.sh +1137 -0
  95. package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
  96. package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
  97. package/evals/integration/test_goal_fit_hook.sh +69 -4
  98. package/evals/integration/test_goal_fit_rederive.sh +263 -0
  99. package/evals/integration/test_install_merge.sh +1176 -0
  100. package/evals/integration/test_kit_identity_trust.sh +393 -0
  101. package/evals/integration/test_mint_attestation.sh +373 -0
  102. package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
  103. package/evals/integration/test_publish_delivery.sh +269 -0
  104. package/evals/integration/test_reconcile_soundness.sh +528 -0
  105. package/evals/integration/test_resolvefirststep_security.sh +208 -0
  106. package/evals/integration/test_session_resume_roundtrip.sh +286 -0
  107. package/evals/integration/test_trust_checkpoint.sh +325 -0
  108. package/evals/integration/test_trust_reconcile.sh +293 -0
  109. package/evals/integration/test_verify_cli.sh +208 -0
  110. package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
  111. package/evals/lib/node.sh +0 -6
  112. package/evals/run.sh +47 -0
  113. package/evals/static/test_workflow_skills.sh +6 -13
  114. package/install.sh +0 -7
  115. package/integrations/strands-ts/README.md +25 -15
  116. package/integrations/veritas/flow-agents.adapter.json +1 -2
  117. package/kits/builder/flows/build.flow.json +59 -12
  118. package/kits/builder/kit.json +85 -15
  119. package/kits/builder/skills/continue-work/SKILL.md +116 -0
  120. package/kits/builder/skills/deliver/SKILL.md +36 -6
  121. package/kits/builder/skills/design-probe/SKILL.md +28 -0
  122. package/kits/builder/skills/execute-plan/SKILL.md +9 -1
  123. package/kits/builder/skills/gate-review/SKILL.md +234 -0
  124. package/kits/builder/skills/learning-review/SKILL.md +30 -0
  125. package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
  126. package/kits/builder/skills/plan-work/SKILL.md +13 -1
  127. package/kits/builder/skills/pull-work/SKILL.md +19 -0
  128. package/kits/knowledge/adapters/default-store/index.js +38 -0
  129. package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
  130. package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
  131. package/kits/knowledge/docs/store-contract.md +314 -0
  132. package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
  133. package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
  134. package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
  135. package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
  136. package/kits/knowledge/evals/entities/suite.test.js +40 -0
  137. package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
  138. package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
  139. package/kits/knowledge/evals/retirement/suite.test.js +145 -0
  140. package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
  141. package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
  142. package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
  143. package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
  144. package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
  145. package/kits/knowledge/kit.json +51 -1
  146. package/package.json +6 -6
  147. package/packaging/conformance/README.md +10 -2
  148. package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
  149. package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
  150. package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
  151. package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
  152. package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
  153. package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
  154. package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
  155. package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
  156. package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
  157. package/packaging/conformance/run-conformance.js +1 -1
  158. package/scripts/README.md +2 -1
  159. package/scripts/build-universal-bundles.js +0 -1
  160. package/scripts/ci/mint-attestation.js +221 -0
  161. package/scripts/ci/trust-reconcile.js +545 -0
  162. package/scripts/hooks/config-protection.js +423 -1
  163. package/scripts/hooks/evidence-capture.js +348 -0
  164. package/scripts/hooks/lib/liveness-read.js +113 -0
  165. package/scripts/hooks/run-hook.js +6 -1
  166. package/scripts/hooks/stop-goal-fit.js +1524 -79
  167. package/scripts/hooks/workflow-steering.js +135 -5
  168. package/scripts/install-codex-home.sh +39 -0
  169. package/scripts/install-merge.js +330 -0
  170. package/scripts/repair-command-log.js +115 -0
  171. package/src/cli/init.ts +218 -20
  172. package/src/cli/validate-workflow-artifacts.ts +18 -2
  173. package/src/cli/verify.ts +100 -0
  174. package/src/cli/workflow-sidecar.ts +2127 -84
  175. package/src/cli.ts +2 -3
  176. package/src/lib/flow-resolver.ts +369 -0
  177. package/src/tools/build-universal-bundles.ts +34 -21
  178. package/src/tools/generate-context-map.ts +3 -17
  179. package/src/tools/validate-source-tree.ts +44 -104
  180. package/build/src/tools/filter-installed-packs.d.ts +0 -2
  181. package/build/src/tools/filter-installed-packs.js +0 -135
  182. package/packaging/packs.json +0 -49
  183. package/scripts/filter-installed-packs.js +0 -2
  184. package/src/tools/filter-installed-packs.ts +0 -132
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env bash
2
+ # test_mint_attestation.sh — Integration eval for CI trust anchor Phase 2.
3
+ #
4
+ # Proves scripts/ci/mint-attestation.js behavior:
5
+ # 1. FAIL-OPEN-LOCAL: exits 0 with no OIDC identity; writes the unsigned in-toto
6
+ # statement to trust.attestation.intoto.json; status "unsigned" recorded.
7
+ # 2. MOCK-SIGNER-ROUND-TRIP: toDsseEnvelope(statement, mockSigner) +
8
+ # parseDssePayload round-trips to original statement; PAE bytes the mock
9
+ # signer received == buildPaeBytes(payloadType, statementJson); subject
10
+ # digest == sha256 of the attested artifact.
11
+ # 3. WITH-RESULTS-FILE: correctly reads CI results file written by trust-reconcile.
12
+ # WORKFLOW-YAML: .github/workflows/trust-reconcile.yml parses; has id-token: write;
13
+ # has mint-attestation step after reconcile; has upload-artifact step.
14
+ #
15
+ # Deterministic, no model spend, no network, self-cleaning.
16
+ # Usage: bash evals/integration/test_mint_attestation.sh
17
+
18
+ set -uo pipefail
19
+
20
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
21
+ MINT="$ROOT/scripts/ci/mint-attestation.js"
22
+
23
+ TMP="$(mktemp -d)"
24
+ errors=0
25
+
26
+ _pass() { echo " PASS: $1"; }
27
+ _fail() { echo " FAIL: $1"; errors=$((errors + 1)); }
28
+
29
+ cleanup() { rm -rf "$TMP"; }
30
+ trap cleanup EXIT
31
+
32
+ # TEST 1: FAIL-OPEN-LOCAL
33
+ echo ""
34
+ echo "=== TEST 1: FAIL-OPEN-LOCAL — no OIDC => unsigned, exit 0 ==="
35
+
36
+ ATTEST_DIR1="$TMP/attest1"
37
+ mkdir -p "$ATTEST_DIR1"
38
+ RUNNER_TEMP1="$TMP/runner1"
39
+ mkdir -p "$RUNNER_TEMP1"
40
+
41
+ out1=$(
42
+ env -u ACTIONS_ID_TOKEN_REQUEST_URL \
43
+ -u SIGSTORE_ID_TOKEN \
44
+ -u GITHUB_ACTIONS \
45
+ ATTESTATION_OUT_DIR="$ATTEST_DIR1" \
46
+ RUNNER_TEMP="$RUNNER_TEMP1" \
47
+ node "$MINT" 2>&1
48
+ )
49
+ exit1=$?
50
+
51
+ if [[ $exit1 -eq 0 ]]; then
52
+ _pass "FAIL-OPEN-LOCAL: exits 0 (fail-open, no OIDC)"
53
+ else
54
+ _fail "FAIL-OPEN-LOCAL: expected exit 0, got $exit1 — output: $out1"
55
+ fi
56
+
57
+ if echo "$out1" | grep -q "status: unsigned"; then
58
+ _pass "FAIL-OPEN-LOCAL: stdout contains 'status: unsigned'"
59
+ else
60
+ _fail "FAIL-OPEN-LOCAL: expected 'status: unsigned' in stdout, got: $out1"
61
+ fi
62
+
63
+ if [[ -f "$ATTEST_DIR1/trust.attestation.intoto.json" ]]; then
64
+ _pass "FAIL-OPEN-LOCAL: trust.attestation.intoto.json written"
65
+ else
66
+ _fail "FAIL-OPEN-LOCAL: trust.attestation.intoto.json not found in $ATTEST_DIR1"
67
+ fi
68
+
69
+ if [[ ! -f "$ATTEST_DIR1/trust.attestation.sig.json" ]]; then
70
+ _pass "FAIL-OPEN-LOCAL: trust.attestation.sig.json absent (correct for unsigned path)"
71
+ else
72
+ _fail "FAIL-OPEN-LOCAL: trust.attestation.sig.json unexpectedly present"
73
+ fi
74
+
75
+ if [[ -f "$ATTEST_DIR1/trust.attestation.status.json" ]]; then
76
+ _pass "FAIL-OPEN-LOCAL: trust.attestation.status.json written"
77
+ else
78
+ _fail "FAIL-OPEN-LOCAL: trust.attestation.status.json not found"
79
+ fi
80
+
81
+ # Verify statement + status shape using a script file
82
+ VERIFY_SCRIPT1="$TMP/verify1.js"
83
+ node - "$ATTEST_DIR1/trust.attestation.intoto.json" \
84
+ "$ATTEST_DIR1/trust.attestation.status.json" << 'NODE'
85
+ const fs = require("fs");
86
+ const [,, intotoPath, statusPath] = process.argv;
87
+ const errors = [];
88
+
89
+ const stmt = JSON.parse(fs.readFileSync(intotoPath, "utf8"));
90
+ if (stmt._type !== "https://in-toto.io/Statement/v1")
91
+ errors.push("_type wrong: " + stmt._type);
92
+ if (stmt.predicateType !== "https://kontourai.dev/ci-verify/v1")
93
+ errors.push("predicateType wrong: " + stmt.predicateType);
94
+ if (!Array.isArray(stmt.subject) || stmt.subject.length === 0)
95
+ errors.push("subject must be non-empty array");
96
+ else {
97
+ const sub = stmt.subject[0];
98
+ if (!sub.name || !sub.digest || !sub.digest.sha256)
99
+ errors.push("subject[0] must have name and digest.sha256");
100
+ }
101
+ if (!stmt.predicate || !stmt.predicate.commit_sha)
102
+ errors.push("predicate.commit_sha missing");
103
+ if (!Array.isArray(stmt.predicate.canonical_commands))
104
+ errors.push("predicate.canonical_commands must be array");
105
+ if (typeof stmt.predicate.reconciled !== "boolean")
106
+ errors.push("predicate.reconciled must be boolean");
107
+ if (!stmt.predicate.built_at)
108
+ errors.push("predicate.built_at missing");
109
+
110
+ const status = JSON.parse(fs.readFileSync(statusPath, "utf8"));
111
+ if (status.status !== "unsigned")
112
+ errors.push("status.status expected 'unsigned', got " + status.status);
113
+ if (!status.reason)
114
+ errors.push("status.reason missing");
115
+ if (!status.output_path)
116
+ errors.push("status.output_path missing");
117
+
118
+ if (errors.length > 0) {
119
+ console.error("STATEMENT ERRORS:\n" + errors.join("\n"));
120
+ process.exit(1);
121
+ }
122
+ console.log("_type=" + stmt._type);
123
+ console.log("predicateType=" + stmt.predicateType);
124
+ console.log("subject=" + stmt.subject[0].name + " sha256=" + stmt.subject[0].digest.sha256.slice(0,16) + "...");
125
+ console.log("status=" + JSON.stringify(status));
126
+ NODE
127
+ if [[ $? -eq 0 ]]; then
128
+ _pass "FAIL-OPEN-LOCAL: statement and status file have correct shape"
129
+ else
130
+ _fail "FAIL-OPEN-LOCAL: statement or status shape incorrect"
131
+ fi
132
+
133
+ # TEST 2: MOCK-SIGNER-ROUND-TRIP
134
+ echo ""
135
+ echo "=== TEST 2: MOCK-SIGNER-ROUND-TRIP — DSSE/PAE + subject-digest proof ==="
136
+
137
+ node - "$ATTEST_DIR1/trust.attestation.intoto.json" << 'NODE'
138
+ // Exercises DSSE signing with a mock signer:
139
+ // toDsseEnvelope(statement, mockSigner) => envelope
140
+ // parseDssePayload(envelope) => round-trip must equal original statement
141
+ // capturedPaeBytes == buildPaeBytes(payloadType, JSON.stringify(statement))
142
+ // subject digest == sha256(predicate JSON) for synthesized subject
143
+
144
+ const fs = require("fs");
145
+ const crypto = require("crypto");
146
+
147
+ async function run() {
148
+ const { toDsseEnvelope, parseDssePayload, buildPaeBytes } =
149
+ await import("@kontourai/surface");
150
+
151
+ const errors = [];
152
+ const statement = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
153
+
154
+ // Deterministic mock signer capturing PAE bytes.
155
+ let capturedPaeBytes = null;
156
+ const mockSigner = {
157
+ keyid: "test-ci-mint-mock",
158
+ sign: async (paeBytes) => {
159
+ capturedPaeBytes = paeBytes;
160
+ return Buffer.from("mock-ci-mint-sig").toString("base64");
161
+ },
162
+ };
163
+
164
+ const envelope = await toDsseEnvelope(statement, mockSigner);
165
+
166
+ if (envelope.payloadType !== "application/vnd.in-toto+json")
167
+ errors.push("payloadType wrong: " + envelope.payloadType);
168
+ else
169
+ console.log("payloadType: " + envelope.payloadType);
170
+
171
+ let roundTripped;
172
+ try { roundTripped = parseDssePayload(envelope); }
173
+ catch (e) { errors.push("parseDssePayload threw: " + e.message); }
174
+ if (roundTripped) {
175
+ if (roundTripped._type !== statement._type)
176
+ errors.push("round-trip _type: " + roundTripped._type);
177
+ if (roundTripped.predicateType !== statement.predicateType)
178
+ errors.push("round-trip predicateType: " + roundTripped.predicateType);
179
+ if (!Array.isArray(roundTripped.subject) ||
180
+ roundTripped.subject.length !== statement.subject.length)
181
+ errors.push("round-trip subject length mismatch");
182
+ console.log("parseDssePayload round-trip predicateType=" + roundTripped.predicateType);
183
+ }
184
+
185
+ const statementJson = JSON.stringify(statement);
186
+ const expectedPae = buildPaeBytes("application/vnd.in-toto+json", statementJson);
187
+ if (capturedPaeBytes === null) {
188
+ errors.push("mock signer was never called");
189
+ } else {
190
+ const match = capturedPaeBytes.length === expectedPae.length &&
191
+ capturedPaeBytes.every((b, i) => b === expectedPae[i]);
192
+ if (!match)
193
+ errors.push("PAE bytes: signer received different bytes than buildPaeBytes");
194
+ else {
195
+ const paeStr = Buffer.from(capturedPaeBytes).toString("utf8").slice(0, 40);
196
+ console.log("PAE bytes match buildPaeBytes: " + paeStr + "...");
197
+ }
198
+ }
199
+
200
+ // Subject digest check: for synthesized subject (name=ci-verify-results),
201
+ // digest.sha256 must equal sha256(JSON.stringify(predicate)).
202
+ const sub = statement.subject[0];
203
+ if (sub.name === "ci-verify-results") {
204
+ const predicateJson = JSON.stringify(statement.predicate);
205
+ const expected = crypto.createHash("sha256").update(predicateJson,"utf8").digest("hex");
206
+ if (sub.digest.sha256 !== expected)
207
+ errors.push("subject digest mismatch: got " + sub.digest.sha256 +
208
+ " expected " + expected);
209
+ else
210
+ console.log("subject digest=sha256(predicateJson): " + sub.digest.sha256.slice(0,16) + "...");
211
+ } else {
212
+ if (!/^[0-9a-f]{64}$/.test(sub.digest.sha256))
213
+ errors.push("subject digest not a valid sha256 hex");
214
+ else
215
+ console.log("subject digest (bundle): " + sub.digest.sha256.slice(0,16) + "...");
216
+ }
217
+
218
+ if (!Array.isArray(envelope.signatures) || envelope.signatures.length === 0)
219
+ errors.push("envelope.signatures empty");
220
+ else {
221
+ if (envelope.signatures[0].keyid !== "test-ci-mint-mock")
222
+ errors.push("keyid wrong: " + envelope.signatures[0].keyid);
223
+ console.log("mock sig keyid=" + envelope.signatures[0].keyid);
224
+ }
225
+
226
+ if (errors.length > 0) {
227
+ console.error("MOCK-SIGNER ERRORS:\n" + errors.join("\n"));
228
+ process.exit(1);
229
+ }
230
+ console.log("MOCK-SIGNER-ROUND-TRIP: all assertions passed");
231
+ }
232
+ run().catch(e => { console.error("test threw: " + e.message); process.exit(1); });
233
+ NODE
234
+ if [[ $? -eq 0 ]]; then
235
+ _pass "MOCK-SIGNER-ROUND-TRIP: toDsseEnvelope/parseDssePayload/buildPaeBytes/subject-digest all correct"
236
+ else
237
+ _fail "MOCK-SIGNER-ROUND-TRIP: DSSE round-trip or PAE/subject-digest assertion failed"
238
+ fi
239
+
240
+ # TEST 3: WITH-RESULTS-FILE
241
+ echo ""
242
+ echo "=== TEST 3: WITH-RESULTS-FILE — reads CI results file when present ==="
243
+
244
+ ATTEST_DIR3="$TMP/attest3"
245
+ mkdir -p "$ATTEST_DIR3"
246
+ RUNNER_TEMP3="$TMP/runner3"
247
+ mkdir -p "$RUNNER_TEMP3"
248
+
249
+ # Write synthetic results as trust-reconcile.js would write on success.
250
+ cat > "$RUNNER_TEMP3/ci-trust-reconcile-results.json" << 'JSON'
251
+ {
252
+ "commit_sha": "deadbeef1234567890abcdef",
253
+ "canonical_commands": [
254
+ { "command": "npm run build", "exitCode": 0, "passed": true }
255
+ ],
256
+ "reconciled": false,
257
+ "built_at": "2026-06-27T00:00:00Z"
258
+ }
259
+ JSON
260
+
261
+ out3=$(
262
+ env -u ACTIONS_ID_TOKEN_REQUEST_URL \
263
+ -u SIGSTORE_ID_TOKEN \
264
+ -u GITHUB_ACTIONS \
265
+ ATTESTATION_OUT_DIR="$ATTEST_DIR3" \
266
+ RUNNER_TEMP="$RUNNER_TEMP3" \
267
+ node "$MINT" 2>&1
268
+ )
269
+ exit3=$?
270
+
271
+ if [[ $exit3 -eq 0 ]]; then
272
+ _pass "WITH-RESULTS-FILE: exits 0"
273
+ else
274
+ _fail "WITH-RESULTS-FILE: expected exit 0, got $exit3 — output: $out3"
275
+ fi
276
+
277
+ if echo "$out3" | grep -q "loaded CI results from"; then
278
+ _pass "WITH-RESULTS-FILE: loaded results file (not synthesized)"
279
+ else
280
+ _fail "WITH-RESULTS-FILE: expected 'loaded CI results from' in stdout, got: $out3"
281
+ fi
282
+
283
+ node - "$ATTEST_DIR3/trust.attestation.intoto.json" << 'NODE'
284
+ const fs = require("fs");
285
+ const stmt = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
286
+ const sha = stmt.predicate && stmt.predicate.commit_sha;
287
+ if (sha !== "deadbeef1234567890abcdef") {
288
+ console.error("predicate.commit_sha expected 'deadbeef1234567890abcdef', got " + sha);
289
+ process.exit(1);
290
+ }
291
+ console.log("predicate.commit_sha from results file: " + sha);
292
+ NODE
293
+ if [[ $? -eq 0 ]]; then
294
+ _pass "WITH-RESULTS-FILE: predicate.commit_sha correctly read from results file"
295
+ else
296
+ _fail "WITH-RESULTS-FILE: predicate.commit_sha mismatch or file missing"
297
+ fi
298
+
299
+ # WORKFLOW-YAML structural validation
300
+ echo ""
301
+ echo "=== WORKFLOW-YAML: trust-reconcile.yml structure ==="
302
+
303
+ WORKFLOW_FILE="$ROOT/.github/workflows/trust-reconcile.yml"
304
+
305
+ if [[ ! -f "$WORKFLOW_FILE" ]]; then
306
+ _fail "WORKFLOW-YAML: workflow file not found at $WORKFLOW_FILE"
307
+ else
308
+ yaml_valid=0
309
+
310
+ if command -v python3 >/dev/null 2>&1 && python3 -c "import yaml" 2>/dev/null; then
311
+ if python3 - "$WORKFLOW_FILE" << 'PY' 2>/dev/null
312
+ import sys, yaml
313
+ try:
314
+ yaml.safe_load(open(sys.argv[1]).read())
315
+ sys.exit(0)
316
+ except yaml.YAMLError as e:
317
+ print("YAML error: " + str(e))
318
+ sys.exit(1)
319
+ PY
320
+ then
321
+ _pass "WORKFLOW-YAML: parses (python3 yaml)"
322
+ yaml_valid=1
323
+ else
324
+ _fail "WORKFLOW-YAML: failed python3 yaml parse"
325
+ yaml_valid=1
326
+ fi
327
+ fi
328
+
329
+ if [[ $yaml_valid -eq 0 ]] && command -v yamllint >/dev/null 2>&1; then
330
+ if yamllint -d relaxed "$WORKFLOW_FILE" >/dev/null 2>&1; then
331
+ _pass "WORKFLOW-YAML: parses (yamllint)"; yaml_valid=1
332
+ else
333
+ _fail "WORKFLOW-YAML: failed yamllint"; yaml_valid=1
334
+ fi
335
+ fi
336
+
337
+ if grep -q "id-token: write" "$WORKFLOW_FILE"; then
338
+ _pass "WORKFLOW-YAML: has 'id-token: write'"
339
+ else
340
+ _fail "WORKFLOW-YAML: 'id-token: write' not found"
341
+ fi
342
+
343
+ if grep -q "mint-attestation.js" "$WORKFLOW_FILE"; then
344
+ _pass "WORKFLOW-YAML: has mint-attestation.js step"
345
+ else
346
+ _fail "WORKFLOW-YAML: mint-attestation.js step not found"
347
+ fi
348
+
349
+ if grep -q "upload-artifact" "$WORKFLOW_FILE"; then
350
+ _pass "WORKFLOW-YAML: has upload-artifact step"
351
+ else
352
+ _fail "WORKFLOW-YAML: upload-artifact step not found"
353
+ fi
354
+
355
+ reconcile_line=$(grep -n "trust-reconcile.js" "$WORKFLOW_FILE" | head -1 | cut -d: -f1)
356
+ mint_line=$(grep -n "mint-attestation.js" "$WORKFLOW_FILE" | head -1 | cut -d: -f1)
357
+ if [[ -n "$reconcile_line" && -n "$mint_line" && "$mint_line" -gt "$reconcile_line" ]]; then
358
+ _pass "WORKFLOW-YAML: mint-attestation comes after trust-reconcile step"
359
+ else
360
+ _fail "WORKFLOW-YAML: mint-attestation must appear after trust-reconcile"
361
+ fi
362
+ fi
363
+
364
+ # Summary
365
+ echo ""
366
+ echo "────────────────────────────────────────────"
367
+ if [[ $errors -eq 0 ]]; then
368
+ echo "test_mint_attestation: all checks passed."
369
+ exit 0
370
+ else
371
+ echo "test_mint_attestation: $errors check(s) failed."
372
+ exit 1
373
+ fi