Skip to content

Commit 0979b90

Browse files
Merge pull request #13 from signalprism/run4-canon-bundle-domain-pack-prism
Run 4: Cryptographic Binding of Authority Contract Outputs
2 parents 50ca5a0 + 01fa9a9 commit 0979b90

13 files changed

Lines changed: 1561 additions & 4 deletions

.github/workflows/ci.yml

Lines changed: 163 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ jobs:
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

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ node_modules/
22
meaning.json
33
# prevent accidental root dist rebuild noise
44
/dist/tmp
5-
5+
# Editor swap/backup files
6+
*.swp
7+
*.swo
8+
*.swx
9+
*~
10+
.DS_Store

AUTHORITY_CONTRACT.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"allowed_mutation_classes": [],
3333

3434
"escalation": {
35-
"tier": "medium",
35+
"tier": "high",
3636
"allow_escalation": false
3737
},
3838

INTENT.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"mode": "normal",
3-
"intent": "Dependency + docs updates",
3+
"intent": "Run 4: add CI-native signature binding and verification",
44
"declared_authority": "high"
55
}

keys/prism-ci-signing.pub.asc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
3+
mDMEaaEdcBYJKwYBBAHaRw8BAQdArZ9kz8hFCZ7LdwMqzX0oorkAWNAPUXTWFU6s
4+
0365RsG0MFNpZ25hbCAmIFByaXNtIENJIFNpZ25pbmcgPGNpQHNpZ25hbHByaXNt
5+
LmxvY2FsPoiQBBMWCAA4FiEEUSfOu8X2W+2JW8ExTqfv08u7+ZUFAmmhHXACGwMF
6+
CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQTqfv08u7+ZViMgD9EW8nlQkSEpbk
7+
1fVHTO7mHtn5fCV+p5dYhBqULwLClTABANlYmrvkce+Pm2y8pzpKY1qoLjdQ69nq
8+
eaWNPXcbiFAA
9+
=YOk3
10+
-----END PGP PUBLIC KEY BLOCK-----
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "sp.authority_contract.v1.1.schema.json",
4+
"title": "Signal & Prism — Authority Contract v1.1",
5+
"type": "object",
6+
"additionalProperties": true,
7+
8+
"required": [
9+
"contract_version",
10+
"declared",
11+
"boundary",
12+
"classification",
13+
"authority",
14+
"gate_result",
15+
"integrity"
16+
],
17+
18+
"properties": {
19+
20+
"contract_version": {
21+
"type": "string",
22+
"const": "sp.authority_contract.v1.1"
23+
},
24+
25+
"declared": {
26+
"type": "object",
27+
"description": "Declared intent and authority assertions.",
28+
"additionalProperties": true
29+
},
30+
31+
"boundary": {
32+
"type": "object",
33+
"description": "Observed mutation boundary (e.g., PR diff metadata).",
34+
"required": ["diff_hash"],
35+
"additionalProperties": true,
36+
"properties": {
37+
"diff_hash": {
38+
"type": "string",
39+
"pattern": "^[a-fA-F0-9]{64}$"
40+
}
41+
}
42+
},
43+
44+
"classification": {
45+
"type": "object",
46+
"description": "Deterministic classification results.",
47+
"required": ["action_class"],
48+
"additionalProperties": true,
49+
"properties": {
50+
"action_class": {
51+
"type": "string"
52+
}
53+
}
54+
},
55+
56+
"authority": {
57+
"type": "object",
58+
"description": "Inferred authority required for production execution.",
59+
"required": ["authority_if_production"],
60+
"additionalProperties": true,
61+
"properties": {
62+
"authority_if_production": {
63+
"type": "string"
64+
}
65+
}
66+
},
67+
68+
"gate_result": {
69+
"type": "string",
70+
"enum": ["allow", "limit", "deny", "warn", "review_required"]
71+
},
72+
73+
"integrity": {
74+
"type": "object",
75+
"description": "Tamper-evident signature binding envelope.",
76+
"required": [
77+
"canonicalization",
78+
"hash_algorithm",
79+
"artifact_hash",
80+
"intent_hash",
81+
"diff_hash",
82+
"signature",
83+
"signing_key_id",
84+
"timestamp"
85+
],
86+
"additionalProperties": true,
87+
"properties": {
88+
89+
"canonicalization": {
90+
"type": "string",
91+
"const": "sp.canonicalization.v1"
92+
},
93+
94+
"hash_algorithm": {
95+
"type": "string",
96+
"enum": ["sha256"]
97+
},
98+
99+
"artifact_hash": {
100+
"type": "string",
101+
"pattern": "^[a-fA-F0-9]{64}$"
102+
},
103+
104+
"intent_hash": {
105+
"type": "string",
106+
"pattern": "^[a-fA-F0-9]{64}$"
107+
},
108+
109+
"diff_hash": {
110+
"type": "string",
111+
"pattern": "^[a-fA-F0-9]{64}$"
112+
},
113+
114+
"signature": {
115+
"type": "string",
116+
"minLength": 16
117+
},
118+
119+
"signature_format": {
120+
"type": "string",
121+
"enum": [
122+
"gpg-detached-base64",
123+
"gpg-detached-ascii-armored",
124+
"raw-base64",
125+
"unknown"
126+
]
127+
},
128+
129+
"signing_key_id": {
130+
"type": "string",
131+
"minLength": 8
132+
},
133+
134+
"ci_run_id": {
135+
"type": "string"
136+
},
137+
138+
"timestamp": {
139+
"type": "string",
140+
"format": "date-time"
141+
}
142+
}
143+
}
144+
}
145+
}

0 commit comments

Comments
 (0)