@kontourai/flow-agents 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/CODEOWNERS +29 -0
- package/.github/actions/trust-verify/action.yml +145 -0
- package/.github/workflows/ci.yml +11 -4
- package/.github/workflows/kit-gates-demo.yml +2 -2
- package/.github/workflows/publish-npm.yml +10 -2
- package/.github/workflows/release-please.yml +1 -1
- package/.github/workflows/trust-reconcile.yml +113 -0
- package/AGENTS.md +13 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +4 -4
- package/README.md +1 -0
- package/agents/tool-planner.json +1 -1
- package/build/src/cli/init.js +242 -20
- package/build/src/cli/validate-workflow-artifacts.js +19 -2
- package/build/src/cli/verify.d.ts +1 -0
- package/build/src/cli/verify.js +90 -0
- package/build/src/cli/workflow-sidecar.d.ts +300 -8
- package/build/src/cli/workflow-sidecar.js +1934 -83
- package/build/src/cli.js +2 -3
- package/build/src/lib/flow-resolver.d.ts +82 -0
- package/build/src/lib/flow-resolver.js +237 -0
- package/build/src/tools/build-universal-bundles.js +34 -22
- package/build/src/tools/generate-context-map.js +3 -16
- package/build/src/tools/validate-source-tree.d.ts +1 -1
- package/build/src/tools/validate-source-tree.js +42 -162
- package/context/contracts/artifact-contract.md +10 -0
- package/context/contracts/delivery-contract.md +1 -0
- package/context/contracts/review-contract.md +1 -0
- package/context/contracts/verification-contract.md +2 -0
- package/context/gate-awareness.md +39 -0
- package/context/scripts/hooks/stop-goal-fit.js +632 -70
- package/docs/adr/0001-flow-agents-consumes-flow.md +1 -1
- package/docs/adr/0002-flow-kits-as-extension-unit.md +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +2 -0
- package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +2 -0
- package/docs/adr/0007-skill-audit.md +1 -1
- package/docs/adr/0009-canonical-hook-core-kit-boundary.md +95 -0
- package/docs/adr/0010-workflow-trust-state-as-hachure-bundle.md +139 -0
- package/docs/adr/0011-mcp-posture.md +100 -0
- package/docs/adr/0012-agent-coordination-as-liveness-claims.md +119 -0
- package/docs/adr/0013-context-lifecycle.md +151 -0
- package/docs/adr/0014-core-vs-domain-kit-boundary.md +143 -0
- package/docs/adr/0015-flow-flow-agents-boundary-reconciliation.md +120 -0
- package/docs/adr/0016-three-hard-boundary-model.md +71 -0
- package/docs/adr/0017-anti-gaming-trust-security-model.md +155 -0
- package/docs/agent-system-guidebook.md +5 -12
- package/docs/context-map.md +4 -10
- package/docs/index.md +3 -2
- package/docs/integrations/framework-adapter.md +19 -6
- package/docs/integrations/index.md +2 -2
- package/docs/north-star.md +4 -4
- package/docs/operating-layers.md +3 -3
- package/docs/plans/adr-0010-phase2-gate-recompute.md +55 -0
- package/docs/repository-structure.md +2 -2
- package/docs/skills-map.md +1 -0
- package/docs/spec/runtime-hook-surface.md +62 -9
- package/docs/standards-register.md +3 -3
- package/docs/survey-utterance-check.md +1 -1
- package/docs/trust-anchor-adoption.md +197 -0
- package/docs/verifiable-trust.md +95 -0
- package/docs/veritas-integration.md +2 -2
- package/docs/workflow-usage-guide.md +69 -0
- package/evals/acceptance/DEMO-false-completion.md +144 -0
- package/evals/acceptance/demo-cast.sh +92 -0
- package/evals/acceptance/demo-false-completion.sh +72 -0
- package/evals/acceptance/demo-real-evidence.sh +104 -0
- package/evals/acceptance/demo.tape +29 -0
- package/evals/acceptance/prove-capture-teeth-declared.sh +335 -0
- package/evals/acceptance/prove-capture-teeth.sh +114 -0
- package/evals/acceptance/prove-teeth.sh +105 -0
- package/evals/ci/antigaming-suite.sh +54 -0
- package/evals/ci/run-baseline.sh +2 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/invalid-missing-extension-asset/kit.json +20 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/flows/review.flow.json +26 -0
- package/evals/fixtures/flow-kit-repository/valid-unknown-extension/kit.json +18 -0
- package/evals/integration/test_builder_step_producers.sh +379 -0
- package/evals/integration/test_bundle_install.sh +35 -71
- package/evals/integration/test_bundle_lifecycle.sh +39 -2
- package/evals/integration/test_captured_fail_reconciliation.sh +820 -0
- package/evals/integration/test_checkpoint_signing.sh +489 -0
- package/evals/integration/test_claim_lookup.sh +352 -0
- package/evals/integration/test_command_log_integrity.sh +275 -0
- package/evals/integration/test_context_map.sh +0 -2
- package/evals/integration/test_dual_emit_flow_step.sh +278 -0
- package/evals/integration/test_enforcer_expects_driven.sh +281 -0
- package/evals/integration/test_evidence_capture_hook.sh +185 -0
- package/evals/integration/test_flow_kit_repository.sh +2 -0
- package/evals/integration/test_flowdef_session_activation.sh +273 -0
- package/evals/integration/test_flowdef_session_history_preservation.sh +250 -0
- package/evals/integration/test_gate_bypass_chain.sh +448 -0
- package/evals/integration/test_gate_lockdown.sh +1137 -0
- package/evals/integration/test_gate_review_inquiry_records.sh +399 -0
- package/evals/integration/test_goal_fit_escape_hatch.sh +73 -0
- package/evals/integration/test_goal_fit_hook.sh +69 -4
- package/evals/integration/test_goal_fit_rederive.sh +263 -0
- package/evals/integration/test_install_merge.sh +1176 -0
- package/evals/integration/test_mint_attestation.sh +373 -0
- package/evals/integration/test_phase_map_and_gate_claim.sh +365 -0
- package/evals/integration/test_publish_delivery.sh +269 -0
- package/evals/integration/test_reconcile_soundness.sh +528 -0
- package/evals/integration/test_resolvefirststep_security.sh +208 -0
- package/evals/integration/test_session_resume_roundtrip.sh +286 -0
- package/evals/integration/test_trust_checkpoint.sh +325 -0
- package/evals/integration/test_trust_reconcile.sh +293 -0
- package/evals/integration/test_verify_cli.sh +208 -0
- package/evals/integration/test_workflow_sidecar_writer.sh +549 -34
- package/evals/lib/node.sh +0 -6
- package/evals/run.sh +45 -0
- package/evals/static/test_workflow_skills.sh +6 -13
- package/install.sh +0 -7
- package/integrations/strands-ts/README.md +25 -15
- package/integrations/veritas/flow-agents.adapter.json +1 -2
- package/kits/builder/flows/build.flow.json +59 -12
- package/kits/builder/kit.json +85 -15
- package/kits/builder/skills/continue-work/SKILL.md +116 -0
- package/kits/builder/skills/deliver/SKILL.md +36 -6
- package/kits/builder/skills/design-probe/SKILL.md +28 -0
- package/kits/builder/skills/execute-plan/SKILL.md +9 -1
- package/kits/builder/skills/gate-review/SKILL.md +234 -0
- package/kits/builder/skills/learning-review/SKILL.md +30 -0
- package/kits/builder/skills/pickup-probe/SKILL.md +29 -0
- package/kits/builder/skills/plan-work/SKILL.md +13 -1
- package/kits/builder/skills/pull-work/SKILL.md +19 -0
- package/kits/knowledge/adapters/default-store/index.js +38 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1620 -0
- package/kits/knowledge/adapters/obsidian-store/index.js +36 -6
- package/kits/knowledge/docs/store-contract.md +314 -0
- package/kits/knowledge/evals/audit-freshness/suite.test.js +368 -0
- package/kits/knowledge/evals/canonicalize-category/suite.test.js +383 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +111 -0
- package/kits/knowledge/evals/detect-contradictions/suite.test.js +324 -0
- package/kits/knowledge/evals/entities/suite.test.js +40 -0
- package/kits/knowledge/evals/glossary-sync/suite.test.js +416 -0
- package/kits/knowledge/evals/hygiene-review/suite.test.js +396 -0
- package/kits/knowledge/evals/retirement/suite.test.js +145 -0
- package/kits/knowledge/flows/audit-freshness.flow.json +44 -0
- package/kits/knowledge/flows/canonicalize-category.flow.json +44 -0
- package/kits/knowledge/flows/detect-contradictions.flow.json +44 -0
- package/kits/knowledge/flows/glossary-sync.flow.json +61 -0
- package/kits/knowledge/flows/hygiene-review.flow.json +43 -0
- package/kits/knowledge/kit.json +51 -1
- package/package.json +4 -4
- package/packaging/conformance/README.md +10 -2
- package/packaging/conformance/fixtures/evidence-capture--allow-records-command.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-bundle-disputed-claim.json +29 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-capture-contradicts-claimed-pass.json +30 -0
- package/packaging/conformance/fixtures/stop-goal-fit--block-mode.json +23 -0
- package/packaging/conformance/fixtures/stop-goal-fit--off-mode.json +24 -0
- package/packaging/conformance/fixtures/stop-goal-fit--warn-active-delivery.json +5 -2
- package/packaging/conformance/fixtures/stop-goal-fit--warn-no-bundle.json +23 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-active-prompt.json +30 -0
- package/packaging/conformance/fixtures/workflow-steering--reground-session-start.json +30 -0
- package/packaging/conformance/run-conformance.js +1 -1
- package/scripts/README.md +2 -1
- package/scripts/build-universal-bundles.js +0 -1
- package/scripts/ci/mint-attestation.js +221 -0
- package/scripts/ci/trust-reconcile.js +545 -0
- package/scripts/hooks/config-protection.js +423 -1
- package/scripts/hooks/evidence-capture.js +348 -0
- package/scripts/hooks/lib/liveness-read.js +113 -0
- package/scripts/hooks/run-hook.js +6 -1
- package/scripts/hooks/stop-goal-fit.js +1471 -79
- package/scripts/hooks/workflow-steering.js +135 -5
- package/scripts/install-codex-home.sh +39 -0
- package/scripts/install-merge.js +330 -0
- package/src/cli/init.ts +218 -20
- package/src/cli/validate-workflow-artifacts.ts +18 -2
- package/src/cli/verify.ts +100 -0
- package/src/cli/workflow-sidecar.ts +2064 -77
- package/src/cli.ts +2 -3
- package/src/lib/flow-resolver.ts +284 -0
- package/src/tools/build-universal-bundles.ts +34 -21
- package/src/tools/generate-context-map.ts +3 -17
- package/src/tools/validate-source-tree.ts +44 -104
- package/build/src/tools/filter-installed-packs.d.ts +0 -2
- package/build/src/tools/filter-installed-packs.js +0 -135
- package/packaging/packs.json +0 -49
- package/scripts/filter-installed-packs.js +0 -2
- 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
|