9.6 KiB
C0-B preactivation update: protected entrypoints now require schema-2 controller evidence and consume verified prebuilt artifact bytes. Repository publication workflows refuse; no privileged candidate code is executed. The original port/recovery history below is preserved, while execution closure defines the current activation and artifact boundary. E5 record/main/endpoint/ledger gates remain mandatory.
Post-activation record (2026-10-16): the closure path has since executed verified live publications — most recently 2026-10-15 — run by an authenticated publisher under a separately admitted operator run, with rollback data retained (the rollback path was not exercised) and admission closed afterwards: credentials removed, no pending records. The repository-resident automatic-publication design below, including
STATIC_PUBLISH_ENABLED, is not the live boundary: repository jobs always refuse, and merging to protectedmainnever publishes.
Static publication on the oss-oo.io forge
This is the deployment boundary for OSS-Singularity/website on the
self-hosted forge (oss-oo.io). It recorded the port of the GitHub publication
contract to Forgejo Actions as the production-path
design of 2026-09-23. Both earlier provider workflows are now historical
reference: repository publication jobs always refuse (see
execution closure), and live publication is the
separately admitted operator execution recorded in the banner above.
What stays identical
The static publication guide continues to describe
the endpoint contract: the restricted fixed-command release interface, the
hoster-side journal with generations and retained rollback, the full HTTP
acceptance (MISS → HIT, bytes, headers, redirects, 404, Telegram bytes,
TLS/origin binding), the Cloudflare zone observation and single purge, and
the rule that a failed acceptance rolls the attempt back and keeps the
original attempt identity. scripts/forgejo_publication.py does not
reimplement any of this: it calls static_publication.transition with the
same SSH, HTTP and edge objects, so a rollback on this boundary exercises
the identical code path the GitHub client used.
What this boundary replaces
| GitHub boundary | Forge boundary |
|---|---|
Workflow-run provenance (workflow_run chaining, run/artifact identity) |
The staged forge CI provenance contract (forgejo_trust, docs/forge-ci-provenance.md): server-evidenced run/job rows at the exact protected main commit plus operator-pinned workflow bytes, re-verified before apply (fresh). Candidate-resident checker and consumer scripts remain outside schema-1 closure; protected policy-bundle integration is still required. |
| Candidate artifact from the separate rehearsal workflow | Same-run double build (UTC build, Pacific/Honolulu rebuild, release-artifact.py verify) plus one more clean-environment rebuild before publishing (source_rebuilt) |
| GitHub deployments API ledger | Append-only oss-static-ledger branch in this repository: ledger/<run>-<attempt>/intent.json before mutation, outcome.json after maintenance, same previous/start/finish semantics |
| GitHub App policy reader | Read-only forge API reads with a scoped token and route allowlist |
production-static environment |
The oss-production concurrency group plus the STATIC_PUBLISH_ENABLED repository secret |
Contents/trees API reads (historical .htaccess, Commons tree) |
Local git reads against explicitly fetched commits |
The report keeps the same fields and acceptance: source_rebuilt=true,
publication_verified=true, rollback_retained=true, plus
pipeline: "forgejo" and the ledger entry as deployment_id.
Access table
| Setting | Purpose |
|---|---|
Secret STATIC_FORGE_TOKEN |
Repository-scoped token for the policy reads and the ledger branch pushes. Read access to branches and commit statuses only; no admin scope. |
Secret CF_RELEASE_TOKEN |
Unchanged from the GitHub boundary: this zone only — cache purge plus the reads needed for zone, settings, DNS and cache-rule verification. |
Secrets STATIC_ORIGIN_IP, STATIC_SSH_USER, STATIC_SSH_PORT |
Unchanged provider connection binding. |
Secrets STATIC_SSH_HOST_KEY, STATIC_SSH_KEY |
Unchanged pinned Ed25519 host key and the restricted fixed-command identity. |
Secret STATIC_RUNTIME_SHA256 |
Unchanged installed nine-file runtime digest. Stored as a secret because this forge instance exposes no write API for action variables. |
Secret STATIC_API_RELEASE_SHA |
Unchanged installed Commons commit. Same reason as above. |
Secret STATIC_PUBLISH_ENABLED |
true enables automatic publication after required checks on protected main; any other value keeps publishing off (the job skips visibly on push). plan dispatches work without it. Enforced again inside the publication client. Historical design only: the closure boundary supersedes it, and repository jobs refuse regardless of this value. |
Secret STATIC_TRUST_PIN |
Operator trust pin for the CI provenance contract (docs/forge-ci-provenance.md): repository, workflow path, workflow bytes digest, events, job name, required context. Absent or invalid pins fail closed (missing_trust_pin / invalid_trust_pin); bootstrapped and re-pinned by the owner, never read from the repository. |
The runner executes on the laboratory server and reaches the static origin directly over the internet; the pinned host key is the same binding the GitHub runner used. No cPanel token or operator SSH identity belongs in this workflow.
Ledger discipline
The oss-static-ledger branch is machine-written. Every publication appends
exactly one directory (ledger/<zero-padded run id>-<attempt>/) containing
the sanitized intent (identity, commit, predecessor hashes, generation,
runtime digest, run identity — no credentials, no file contents) and one
terminal outcome. An intent without a terminal outcome blocks the next
publication exactly like an unresolved GitHub deployment did. Operators
never rewrite, rebase or force-push this branch; recovery closes a blocked
record, it never deletes one.
Blocked-record recovery
Dispatch mode recover closes a blocked ledger record through the same
bounded contract the GitHub boundary had. scripts/forgejo-recovery.py
reuses the static_recovery engine cores unchanged: it re-reads the original
recorded intent from oss-static-ledger, reconciles a retained remote attempt
under its original identity and ticket, and closes the record only after the
resulting live bytes pass the same full HTTP acceptance as a publication —
either as a verified rollback, or as success when only maintenance or record
bookkeeping failed after an already healthy verified site. It never creates a
new intent, adopts a foreign attempt, deletes or rebases the ledger, or resets
endpoint state.
Only the newest record is examined, and only when it has no outcome yet, its
in-progress outcome, or its explicit unresolved outcome; closed, foreign,
malformed or ambiguous records are refused without any mutation. A ledger
fetch that fails never falls back to the runner folder's earlier copy: it
fails closed as forge_git_failed, and only a remote proven to hold no
ledger branch at all answers no_unresolved_publication. A changed endpoint
runtime digest, operator-modified files or a changed provider configuration
refuse recovery and keep the record blocked.
Because the verified-site path executes the recorded tree's build scripts
(git archive plus the same double build and release-artifact.py verify the
publication boundary uses — the GitHub-era artifact consumer it replaced only
ever re-verified downloaded bytes and never executed historical code), the
mechanical path is bounded to the one candidate a publication itself would
trust: before anything is archived, built or asked of the endpoint, the
recorded commit, the dispatch SHA and a freshly read protected main head
must all be the same commit, and that commit's pinned required-status context
must be green — the publication boundary's own pre-build binding. The loader
route refuses any other commit, and the verified-site path additionally
requires the endpoint's own report to name this record's candidate_commit
before it builds or closes positively. It still requires the recorded
descriptor digest; the rollback path reads the recorded predecessor's
site/.htaccess from the local repository, and missing local git objects
fail closed — recovery never downloads history automatically.
An older record — main has already moved past its commit — refuses with
stale_candidate before any execution. Such records, and any record outside
this exact-current-candidate bound, keep the manual, evidence-bound operator
path in the RUNBOOK; re-dispatching the workflow from an older checkout or a
raw SHA is not a supported workaround. This boundary deliberately claims no
general historical byte-artifact recovery: a retained-artifact route that
could verify older candidates without executing their build scripts would be
its own future block.
Recovery shares the oss-production concurrency group with publication, so
it never overlaps a running publication, and a competing ledger writer loses
nothing: the terminal outcome push fail-closes instead of overwriting. It
deliberately does not require STATIC_PUBLISH_ENABLED: an operator may
disable automatic publication during an incident and still close the blocked
record.
Not ported (yet)
- CodeQL: the GitHub org-level default setup has no forge equivalent; the
required-check policy pins the forge
Repository checksstatus instead.