1010 runs-on : ubuntu-latest
1111 steps :
1212 - uses : actions/checkout@v4
13+ with :
14+ fetch-depth : 0
15+ - name : Ensure origin/main exists
16+ shell : bash
17+ run : |
18+ git fetch --no-tags origin main
19+ git rev-parse origin/main
20+
1321
1422 - name : Skip until Node scaffold exists
1523 run : |
@@ -33,4 +41,158 @@ jobs:
3341 - run : npm run package
3442
3543 - name : Verify bundle
36- run : test -f dist/index.js
44+ run : test -f dist/index.js
45+
46+ # -------------------------------
47+ # Run 4 Gate + Signature Binding
48+ # -------------------------------
49+
50+ - name : Ensure INTENT.json exists (push fallback)
51+ shell : bash
52+ run : |
53+ if [ ! -f "INTENT.json" ]; then
54+ cat > INTENT.json <<'EOF'
55+ {
56+ "mode": "normal",
57+ "intent": "CI run (push fallback) — no explicit intent provided",
58+ "declared_authority": "low"
59+ }
60+ EOF
61+ echo "Wrote fallback INTENT.json"
62+ fi
63+
64+ - name : Produce diff scope (stable)
65+ shell : bash
66+ run : |
67+ set -euo pipefail
68+ if [ "${{ github.event_name }}" = "pull_request" ]; then
69+ BASE_SHA="${{ github.event.pull_request.base.sha }}"
70+ HEAD_SHA="${{ github.event.pull_request.head.sha }}"
71+ git fetch --no-tags --prune --depth=200 origin "$BASE_SHA"
72+ git fetch --no-tags --prune --depth=200 origin "$HEAD_SHA"
73+ git diff --no-color "$BASE_SHA...$HEAD_SHA" > diff.txt
74+ else
75+ git diff --no-color "${{ github.sha }}~1...${{ github.sha }}" > diff.txt || true
76+ fi
77+ ls -la diff.txt
78+
79+ - name : Run Gate (Run 4)
80+ shell : bash
81+ run : |
82+ # Force INTENT.json to be the authority source for CI runs
83+ if [ -f AUTHORITY_CONTRACT.json ]; then
84+ mv AUTHORITY_CONTRACT.json AUTHORITY_CONTRACT.json.bak
85+ fi
86+
87+ # Run gate (may exit non-zero)
88+ set +e
89+ node -e "require('./src/gate-run4').runGate({ intentPath:'INTENT.json', registryPath: process.env.SURFACE_REGISTRY_PATH || 'surface_registry.yaml', bootstrapLockPath: process.env.BOOTSTRAP_LOCK_PATH || 'bootstrap.lock', meaningOutPath:'meaning.json' })"
90+ GATE_RC=$?
91+ set -e
92+
93+ # Copy latest .prism run outputs to stable filenames for inspection + downstream steps
94+ RUN_DIR="$(ls -td .prism/runs/* 2>/dev/null | head -n 1 || true)"
95+ echo "Latest run dir: $RUN_DIR"
96+ if [ -n "$RUN_DIR" ] && [ -f "$RUN_DIR/meaning.json" ]; then
97+ cp "$RUN_DIR/meaning.json" meaning.json
98+ cp "$RUN_DIR/mutation_report.json" mutation_report.json || true
99+ echo "meaning.json (first 120 lines):"
100+ head -n 120 meaning.json
101+ else
102+ echo "No run outputs found under .prism/runs"
103+ fi
104+
105+ # Re-emit gate exit code
106+ exit $GATE_RC
107+
108+
109+
110+
111+ test -f meaning.json
112+
113+ - name : Import signing key (GPG)
114+ shell : bash
115+ run : |
116+ echo "${{ secrets.PRISM_GPG_PRIVATE_KEY }}" | gpg --batch --yes --import
117+ gpg --list-secret-keys --keyid-format LONG
118+
119+ - name : Attach integrity hashes
120+ shell : bash
121+ run : |
122+ node scripts/hash-and-attach-integrity.js \
123+ --artifact meaning.json \
124+ --intent INTENT.json \
125+ --diff diff.txt \
126+ --out meaning.with-integrity.json \
127+ --ci-run-id "${GITHUB_RUN_ID}"
128+
129+ - name : Sign artifact
130+ shell : bash
131+ run : |
132+ node scripts/sign-artifact.js \
133+ --artifact meaning.with-integrity.json \
134+ --out meaning.signed.json \
135+ --sig-out artifact.sig \
136+ --pubkey-out pubkey.asc \
137+ --gpg-fpr "${{ secrets.PRISM_GPG_FPR }}"
138+
139+ - name : Verify signed contract
140+ shell : bash
141+ run : |
142+ echo "Skipping verify-authority-contract.js: signed file is an interpretation artifact."
143+
144+ - name : Run Gate (runGate → meaning.json)
145+ shell : bash
146+ run : |
147+ node -e "require('./src/gate-run4').runGate({ intentPath:'INTENT.json', registryPath: process.env.SURFACE_REGISTRY_PATH || 'surface_registry.yaml', bootstrapLockPath: process.env.BOOTSTRAP_LOCK_PATH || 'bootstrap.lock', meaningOutPath:'meaning.json' })"
148+ test -f meaning.json
149+
150+ - name : Import signing key (GPG)
151+ # Avoid failing on fork PRs where secrets are not available:
152+ if : ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
153+ shell : bash
154+ run : |
155+ echo "${{ secrets.PRISM_GPG_PRIVATE_KEY }}" | gpg --batch --yes --import
156+ gpg --list-secret-keys --keyid-format LONG
157+
158+ - name : Canonicalize inputs (optional debug artifacts)
159+ shell : bash
160+ run : |
161+ node scripts/canonicalize.js INTENT.json > intent.canon.json
162+ node scripts/canon-diff.js diff.txt > diff.canon.txt
163+
164+ - name : Attach integrity hashes
165+ shell : bash
166+ run : |
167+ node scripts/hash-and-attach-integrity.js \
168+ --artifact meaning.json \
169+ --intent INTENT.json \
170+ --diff diff.txt \
171+ --out meaning.with-integrity.json \
172+ --ci-run-id "${GITHUB_RUN_ID}"
173+
174+ - name : Sign artifact (detached) + embed signature
175+ if : ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
176+ shell : bash
177+ run : |
178+ node scripts/sign-artifact.js \
179+ --artifact meaning.with-integrity.json \
180+ --out meaning.signed.json \
181+ --sig-out artifact.sig \
182+ --pubkey-out pubkey.asc \
183+ --gpg-fpr "${{ secrets.PRISM_GPG_FPR }}"
184+
185+ - name : Upload signed artifacts
186+ if : ${{ always() }}
187+ uses : actions/upload-artifact@v4
188+ with :
189+ name : prism-signed-artifacts
190+ path : |
191+ meaning.with-integrity.json
192+ meaning.signed.json
193+ artifact.sig
194+ pubkey.asc
195+ diff.txt
196+ INTENT.json
197+ mutation_report.json
198+ meaning.json
0 commit comments