@@ -47,20 +47,45 @@ jobs:
4747 # Run 4 Gate + Signature Binding
4848 # -------------------------------
4949
50- - name : Ensure INTENT.json exists (push fallback )
50+ - name : Select contract source (authority-first )
5151 shell : bash
5252 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"
53+ set -euo pipefail
54+
55+ # Prefer AUTHORITY.json (new)
56+ if [ -f "AUTHORITY.json" ]; then
57+ echo "CONTRACT_PATH=AUTHORITY.json" >> $GITHUB_ENV
58+ echo "CONTRACT_KIND=authority" >> $GITHUB_ENV
59+ exit 0
60+ fi
61+
62+ # Back-compat: AUTHORITY_CONTRACT.json (current repo reality)
63+ if [ -f "AUTHORITY_CONTRACT.json" ]; then
64+ echo "CONTRACT_PATH=AUTHORITY_CONTRACT.json" >> $GITHUB_ENV
65+ echo "CONTRACT_KIND=authority" >> $GITHUB_ENV
66+ echo "WARN: Using AUTHORITY_CONTRACT.json. Consider renaming to AUTHORITY.json."
67+ exit 0
68+ fi
69+
70+ # Legacy support: INTENT.json (old)
71+ if [ -f "INTENT.json" ]; then
72+ echo "CONTRACT_PATH=INTENT.json" >> $GITHUB_ENV
73+ echo "CONTRACT_KIND=intent" >> $GITHUB_ENV
74+ echo "WARN: Using legacy INTENT.json. Migrate to AUTHORITY.json."
75+ exit 0
76+ fi
77+
78+ # No contract present
79+ if [ "${{ github.event_name }}" = "pull_request" ]; then
80+ echo "ERROR: Missing contract file. Provide AUTHORITY.json (preferred) or INTENT.json (legacy)."
81+ exit 2
6282 fi
6383
84+ # For push runs: do NOT invent a contract silently.
85+ echo "No contract file on push; gate will be skipped."
86+ echo "CONTRACT_PATH=" >> $GITHUB_ENV
87+ echo "CONTRACT_KIND=none" >> $GITHUB_ENV
88+
6489 - name : Produce diff scope (stable)
6590 shell : bash
6691 run : |
@@ -79,38 +104,39 @@ jobs:
79104 - name : Run Gate (Run 4)
80105 shell : bash
81106 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
107+ set -euo pipefail
108+
109+ if [ "${CONTRACT_KIND}" = "none" ]; then
110+ echo "Skipping gate (no contract on push)."
111+ exit 0
112+ fi
113+
114+ if { [ -f "AUTHORITY.json" ] || [ -f "AUTHORITY_CONTRACT.json" ]; } && [ -f "INTENT.json" ]; then
115+ echo "ERROR: Authority contract present alongside INTENT.json. Remove INTENT.json."
116+ exit 2
85117 fi
86118
87- # Run gate (may exit non-zero)
88119 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' })"
120+ node -e "require('./src/gate-run4').runGate({ intentPath: process.env.CONTRACT_PATH , registryPath: process.env.SURFACE_REGISTRY_PATH || 'surface_registry.yaml', bootstrapLockPath: process.env.BOOTSTRAP_LOCK_PATH || 'bootstrap.lock', meaningOutPath:'meaning.json' })"
90121 GATE_RC=$?
91122 set -e
92123
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"
124+ echo "Gate exit code: $GATE_RC"
125+ if [ $GATE_RC -ne 0 ]; then
126+ exit $GATE_RC
103127 fi
104128
105- # Re-emit gate exit code
106- exit $GATE_RC
107-
108-
109-
129+ # HARD ASSERT: meaning.json must be valid JSON
130+ if [ ! -s meaning.json ]; then
131+ echo "ERROR: meaning.json missing or empty after gate."
132+ ls -la || true
133+ exit 2
134+ fi
110135
111- test -f meaning.json
136+ node -e "JSON.parse(require('fs').readFileSync(' meaning.json','utf8')); console.log('meaning.json is valid JSON');"
112137
113138 - name : Import signing key (GPG)
139+ if : ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
114140 shell : bash
115141 run : |
116142 echo "${{ secrets.PRISM_GPG_PRIVATE_KEY }}" | gpg --batch --yes --import
@@ -119,80 +145,83 @@ jobs:
119145 - name : Attach integrity hashes
120146 shell : bash
121147 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}"
148+ set -euo pipefail
149+ if [ "${CONTRACT_KIND}" = "none" ]; then
150+ echo "Skipping integrity (no contract on push)."
151+ exit 0
152+ fi
153+
154+ if [ "${CONTRACT_KIND}" = "authority" ]; then
155+ node scripts/hash-and-attach-integrity.js \
156+ --artifact meaning.json \
157+ --authority "${CONTRACT_PATH}" \
158+ --diff diff.txt \
159+ --out meaning.with-integrity.json \
160+ --ci-run-id "${GITHUB_RUN_ID}"
161+ else
162+ node scripts/hash-and-attach-integrity.js \
163+ --artifact meaning.json \
164+ --intent "${CONTRACT_PATH}" \
165+ --diff diff.txt \
166+ --out meaning.with-integrity.json \
167+ --ci-run-id "${GITHUB_RUN_ID}"
168+ fi
128169
129170 - name : Sign artifact
171+ if : ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
130172 shell : bash
131173 run : |
174+ set -euo pipefail
175+ if [ "${CONTRACT_KIND}" = "none" ]; then
176+ echo "Skipping sign (no contract on push)."
177+ exit 0
178+ fi
179+
132180 node scripts/sign-artifact.js \
133181 --artifact meaning.with-integrity.json \
134182 --out meaning.signed.json \
135183 --sig-out artifact.sig \
136184 --pubkey-out pubkey.asc \
137185 --gpg-fpr "${{ secrets.PRISM_GPG_FPR }}"
138186
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:
187+ - name : Verify signed interpretation artifact
152188 if : ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false }}
153189 shell : bash
154190 run : |
155- echo "${{ secrets.PRISM_GPG_PRIVATE_KEY }}" | gpg --batch --yes --import
156- gpg --list-secret-keys --keyid-format LONG
191+ set -euo pipefail
192+ if [ "${CONTRACT_KIND}" = "none" ]; then
193+ echo "Skipping verify (no contract on push)."
194+ exit 0
195+ fi
157196
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
197+ if [ "${CONTRACT_KIND}" = "authority" ]; then
198+ node scripts/verify-interpretation-artifact.js \
199+ --artifact meaning.signed.json \
200+ --authority "${CONTRACT_PATH}" \
201+ --diff diff.txt \
202+ --pubkey pubkey.asc
203+ else
204+ node scripts/verify-interpretation-artifact.js \
205+ --artifact meaning.signed.json \
206+ --intent "${CONTRACT_PATH}" \
207+ --allow-intent-hash \
208+ --diff diff.txt \
209+ --pubkey pubkey.asc
210+ fi
163211
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 }}
212+ - name : Stage artifacts for upload
213+ if : ${{ always() }}
176214 shell : bash
177215 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 }}"
216+ set -euo pipefail
217+ mkdir -p out_artifacts
218+ cp -f meaning.with-integrity.json meaning.signed.json artifact.sig pubkey.asc diff.txt meaning.json mutation_report.json out_artifacts/ 2>/dev/null || true
219+ cp -f INTENT.json out_artifacts/ 2>/dev/null || true
220+ cp -f AUTHORITY.json out_artifacts/ 2>/dev/null || true
184221
185222 - name : Upload signed artifacts
186223 if : ${{ always() }}
187224 uses : actions/upload-artifact@v4
188225 with :
189226 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
227+ path : out_artifacts
0 commit comments