Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions .github/workflows/commons-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: Signed Commons release

on:
workflow_dispatch:
schedule:
- cron: "*/10 * * * *"

permissions:
contents: read

concurrency:
group: commons-production-release
cancel-in-progress: false

jobs:
release:
if: github.repository == 'SignalLayerLabs/Marginal' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: commons-production
timeout-minutes: 10
steps:
- name: Checkout trusted MARGINAL
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
path: marginal
persist-credentials: false

- name: Checkout Commons as data
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: SignalLayerLabs/Marginal-Commons
ref: main
fetch-depth: 0
path: commons-data
persist-credentials: false

- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.13.7"

- name: Install trusted release dependencies
working-directory: marginal
run: >-
python -m pip install
--disable-pip-version-check
--only-binary=:all:
--require-hashes
--no-cache-dir
--requirement requirements/commons-release.txt

- name: Build signed candidate
env:
COMMONS_RELEASE_PRIVATE_KEY_B64URL: ${{ secrets.COMMONS_RELEASE_PRIVATE_KEY_B64URL }}
run: >-
python marginal/scripts/build_commons_release.py
--commons-repo commons-data
--revision HEAD
--output-dir candidate/dist

- name: Independently verify candidate
run: >-
python marginal/scripts/build_commons_release.py
--verify-pack candidate/dist/commons-pack-v1.json
--verify-signature candidate/dist/commons-pack-v1.sig.json

- name: Compare current signed production state
id: production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
shell: bash
run: |
set -euo pipefail
mkdir -p current/dist
pack_status="$(curl --silent --show-error --location --proto '=https' --tlsv1.2 \
--connect-timeout 10 --max-time 30 --max-filesize 3145728 \
--output current/dist/commons-pack-v1.json --write-out '%{http_code}' \
https://marginal-commons.pages.dev/dist/commons-pack-v1.json)"
signature_status="$(curl --silent --show-error --location --proto '=https' --tlsv1.2 \
--connect-timeout 10 --max-time 30 --max-filesize 1048576 \
--output current/dist/commons-pack-v1.sig.json --write-out '%{http_code}' \
https://marginal-commons.pages.dev/dist/commons-pack-v1.sig.json)"
if [ "${signature_status}" = "404" ]; then
test "${pack_status}" = "200"
deployment_page=1
: > current/deployment-urls.txt
while :; do
curl --fail --silent --show-error --proto '=https' --tlsv1.2 \
--connect-timeout 10 --max-time 30 --max-filesize 1048576 \
--header "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/marginal-commons/deployments?env=production&per_page=100&page=${deployment_page}" \
--output current/deployments.json
total_pages="$(DEPLOYMENT_PAGE="${deployment_page}" python - <<'PY'
import json
import os
import re
from pathlib import Path

payload = json.loads(Path("current/deployments.json").read_text(encoding="utf-8"))
if not isinstance(payload, dict) or payload.get("success") is not True:
raise SystemExit("Cloudflare deployment history is invalid")
deployments = payload.get("result")
result_info = payload.get("result_info")
if not isinstance(deployments, list) or not isinstance(result_info, dict):
raise SystemExit("Cloudflare deployment history is invalid")
current_page = result_info.get("page")
total_pages = result_info.get("total_pages")
expected_page = int(os.environ["DEPLOYMENT_PAGE"])
if (
isinstance(current_page, bool)
or not isinstance(current_page, int)
or current_page != expected_page
or isinstance(total_pages, bool)
or not isinstance(total_pages, int)
or not expected_page <= total_pages <= 10_000
):
raise SystemExit("Cloudflare deployment pagination is invalid")
pattern = re.compile(r"https://[a-z0-9-]+\.marginal-commons\.pages\.dev\Z")
with Path("current/deployment-urls.txt").open("a", encoding="utf-8") as output:
for deployment in deployments:
url = deployment.get("url") if isinstance(deployment, dict) else None
if not isinstance(url, str) or pattern.fullmatch(url) is None:
raise SystemExit("Cloudflare deployment history contains an invalid URL")
print(url, file=output)
print(total_pages)
PY
)"
if [ "${deployment_page}" -ge "${total_pages}" ]; then
break
fi
deployment_page=$((deployment_page + 1))
done
while IFS= read -r deployment_url; do
historical_status="$(curl --silent --show-error --proto '=https' --tlsv1.2 \
--connect-timeout 10 --max-time 30 --max-filesize 1048576 \
--output /dev/null --write-out '%{http_code}' \
"${deployment_url}/dist/commons-pack-v1.sig.json")"
if [ "${historical_status}" = "200" ]; then
echo "A signed production deployment already exists; missing current signature fails closed."
exit 1
fi
test "${historical_status}" = "404"
done < current/deployment-urls.txt
echo "deploy=true" >> "${GITHUB_OUTPUT}"
exit 0
fi
test "${signature_status}" = "200"
test "${pack_status}" = "200"
python marginal/scripts/build_commons_release.py \
--verify-pack current/dist/commons-pack-v1.json \
--verify-signature current/dist/commons-pack-v1.sig.json
python marginal/scripts/build_commons_release.py \
--verify-pack candidate/dist/commons-pack-v1.json \
--verify-signature candidate/dist/commons-pack-v1.sig.json \
--current-pack current/dist/commons-pack-v1.json \
--current-signature current/dist/commons-pack-v1.sig.json \
>> "${GITHUB_OUTPUT}"

- name: Deploy signed release
if: steps.production.outputs.deploy == 'true'
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: >-
npx wrangler@4.124.0 pages deploy candidate
--project-name marginal-commons
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ include ROADMAP.md
recursive-include schemas *.json
recursive-include contracts *.json
recursive-include demos *.md *.json *.html *.svg *.jsonl
include scripts/build_commons_release.py
include models/canonical-model-registry-v1.json
include requirements/commons-release.txt

recursive-include assets *.png
1 change: 1 addition & 0 deletions contracts/commons-release-key-v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"algorithm":"ed25519","key_id":"commons-release-962b690a695e079d","not_after_revision":2147483647,"not_before_revision":1,"public_key":"mGvL2Rpdx-A2Rf1bg8BJ0GqI2F1TQ9VK9HeEUYHYeg0","schema_version":"1.0"}
1 change: 1 addition & 0 deletions contracts/commons-release-key-v1.sig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"algorithm":"ed25519","key_id":"commons-root-v1","schema_version":"1.0","signature":"G1LhoyR2ERbXFDY5WpqFQBUjSRZwcQchurVIJUZUV6-lapB8rRUDqmMJjpYaEQ4S9S3t_SGy9dOQLS8ELBy5Dw"}
1 change: 1 addition & 0 deletions contracts/commons-root-key-v1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"algorithm":"ed25519","key_id":"commons-root-v1","public_key":"NLeHfzgR6FIun-jSoeTwKss1qJbEvFNxUNdSzWvNT1U","schema_version":"1.0"}
54 changes: 54 additions & 0 deletions docs/commons-release-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Signed Commons release operations

MARGINAL Commons releases use an offline Ed25519 root to certify an online release key. MARGINAL
runtime distributions contain the root public key only. Each detached release envelope carries the
root-signed release certificate and a release-key signature over the exact pack bytes.

## Key custody and rotation

Keep the root private key offline and outside developer machines, CI, and repository storage. The
online release seed belongs only in the `commons-production` GitHub Environment as
`COMMONS_RELEASE_PRIVATE_KEY_B64URL`. Restrict that environment and the Cloudflare token to the
smallest practical maintainer and deployment scope.

To rotate the online key, create a new closed release certificate with a unique key ID and bounded
revision interval, sign its canonical JSON bytes offline with the root, review the three public
contract files, and update MARGINAL before using the new release seed. Overlap revision intervals
only when an intentional rollout needs it. Do not overwrite an existing key ID with different key
material.

If an online release key may be compromised, remove it from the production environment, stop the
release workflow, and ship a MARGINAL update whose trusted policy no longer accepts its certificate
before resuming publication with a newly certified key. Revision bounds limit where a certificate
is accepted but are not a network revocation mechanism. A root-key compromise requires a new root
anchor and a MARGINAL software update; signatures already trusted by old clients cannot be remotely
revoked.

## Publication behavior

The scheduled and manually dispatched workflow runs only from `SignalLayerLabs/Marginal` `main` in
the protected `commons-production` environment. It checks out Marginal-Commons with full history as
data, reads immutable Git objects, never imports or executes Commons code, builds a deterministic
candidate, and verifies it with both the runtime verifier and `cryptography` before comparison.
The official checkout and Python setup actions are pinned to immutable commits. The signing job
installs only exact-version binary release dependencies accepted by the reviewed SHA-256 lock file;
the private seed is exposed only to the candidate-build step and must never be logged or persisted.

The first signed deployment may replace the existing unsigned production pack when the signature
path is absent and the complete paginated Cloudflare deployment history contains no earlier signed
release. This is the one bootstrap exception. Once any signed production deployment exists, a
missing, malformed,
rollback, or same-revision-conflicting production artifact makes publication fail closed. Wrangler
is pinned to 4.124.0 and deploys only a verified candidate containing the two fixed `dist/` paths.

Runtime consumption has the opposite availability posture: network, signature, schema, and cache
failures return no shared prior and never block local work or Contributor outbox processing. A
signed Commons pack authenticates its release chain and bytes; it does not prove that upstream
observations are correct.

## Authority boundary

Commons remains a non-authoritative prior. Its signatures and lifecycle labels cannot activate
Tool Enforcement, promote Autopilot, change thresholds, override local evidence, or grant any local
authority. Local Only remains the default, sharing still requires explicit Contributor mode, and
Sol, Terra, and Luna priors remain isolated by exact canonical namespace.
108 changes: 108 additions & 0 deletions docs/superpowers/plans/2026-08-21-signed-commons-trust-path.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Signed Commons Trust Path Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Authenticate exact Commons pack bytes through an offline-root-certified release key before MARGINAL caches or uses model-specific priors.

**Architecture:** A stdlib-only strict Ed25519 verifier and closed trust parser sit before the existing pack parser. The client downloads a fixed pack/signature pair, the cache atomically persists one signed artifact with anti-rollback, and trusted release tooling builds from immutable Git objects and signs only after verifying the frozen public chain.

**Tech Stack:** Python 3.10+ stdlib at runtime; pytest, cryptography, jsonschema, Git, GitHub Actions, Cloudflare Wrangler 4.124.0 for tests/release tooling.

**Spec:** `docs/superpowers/specs/2026-08-21-signed-commons-trust-path-design.md`

## Global Constraints

- Keep `pyproject.toml` production `dependencies = []`.
- Never access, display, persist, or log a private signing key.
- Never import or execute Marginal-Commons code or trust its `dist/` directory.
- Keep Commons prior-only, exact-model-isolated, and fail-open at runtime.
- Do not commit, push, merge, switch branches, or modify Marginal-Commons.

---

### Task 1: Strict Ed25519 and signed-envelope verification

**Files:**
- Create: `src/marginal/commons/ed25519.py`
- Create: `src/marginal/commons/trust.py`
- Create: `src/marginal/commons/commons-root-key-v1.json`
- Test: `tests/commons/test_ed25519.py`
- Test: `tests/commons/test_trust.py`

**Interfaces:**
- Produces: `verify_ed25519(public_key: bytes, message: bytes, signature: bytes) -> bool`
- Produces: `verify_signed_pack(pack: bytes, signature: bytes) -> VerifiedCommonsPack`

- [ ] Write RFC 8032 and strict-rejection tests using literal vectors and independently generated cryptography fixtures.
- [ ] Run `pytest -q tests/commons/test_ed25519.py tests/commons/test_trust.py` and observe missing-interface failures.
- [ ] Implement strict base64url, point decoding/subgroup checks, certificate/envelope parsing, and chain verification.
- [ ] Run the targeted tests to green.

### Task 2: Signed atomic cache and fixed-path paired download

**Files:**
- Modify: `src/marginal/commons/cache.py`
- Modify: `src/marginal/commons/client.py`
- Modify: `src/marginal/commons/sync.py`
- Modify: `src/marginal/commons/__init__.py`
- Test: `tests/commons/test_cache.py`
- Test: `tests/commons/test_client.py`
- Test: `tests/commons/test_sync.py`
- Test: `tests/commons/test_local_e2e.py`

**Interfaces:**
- Consumes: `verify_signed_pack(...)`.
- Produces: `CommonsPackDownload(pack: bytes, signature: bytes)` and `CommonsCache.refresh(download)`.

- [ ] Update tests and doubles for paired downloads, legacy-cache rejection, anti-rollback, idempotence, equivocation, exact-model isolation, and fail-open submission.
- [ ] Run the targeted tests and observe API/behavior failures.
- [ ] Implement the paired client, one-object signed cache, source-commit format validation, and sync integration.
- [ ] Run the targeted tests to green.

### Task 3: Immutable-snapshot release builder

**Files:**
- Create: `scripts/build_commons_release.py`
- Test: `tests/commons/test_release_builder.py`

**Interfaces:**
- Produces: CLI accepting a Commons repository/revision and output directory; writes `commons-pack-v1.json` and `commons-pack-v1.sig.json`.

- [ ] Write behavior tests for untrusted-code non-execution, Git entry types, worktree mutation, poisoned JSON, contract drift, deterministic revision/content, docs-only commits, and key mismatch.
- [ ] Run builder tests and observe the missing-script failures.
- [ ] Implement bounded Git-object reads, frozen-contract parsing, deterministic pack generation, public-chain preflight, and environment-only signing.
- [ ] Run builder tests to green.

### Task 4: Workflow, packaging, and operations documentation

**Files:**
- Create: `.github/workflows/commons-release.yml`
- Create: `docs/commons-release-security.md`
- Modify: `pyproject.toml`
- Modify: `MANIFEST.in`
- Modify: `tests/test_packaged_schemas_v2.py`
- Create: `tests/commons/test_release_workflow.py`

**Interfaces:**
- Consumes: release builder CLI and verifier CLI mode.
- Produces: scheduled/dispatch-only fail-closed production publication and distributable public trust data.

- [ ] Write workflow and package-artifact behavior tests and observe failures.
- [ ] Add the workflow, public-trust package data, and concise operations/security documentation.
- [ ] Run packaging/workflow tests to green.

### Task 5: Complete verification and runtime rebuild

**Files:**
- Rebuild: `plugins/marginal/runtime/marginal_runtime.pyz`
- Rebuild: `plugins/marginal/runtime/provenance.json`

**Interfaces:**
- Consumes: all implementation and tests.
- Produces: verified source tree, sdist/wheel, and deterministic Codex zipapp.

- [ ] Run targeted Commons tests.
- [ ] Run `ruff format --check .`, `ruff check .`, `mypy src/marginal`, and `pytest -q` in `/tmp/marginal-signed-commons-venv`.
- [ ] Run `python scripts/build_codex_plugin.py` followed by `python scripts/build_codex_plugin.py --check`.
- [ ] Run `python -m build`, `python -m twine check dist/*`, and `git diff --check`.
- [ ] Inspect `git status`, `git diff --stat`, the complete diff, runtime SHA-256, and provenance without committing.
Loading
Loading