# C0-B execution closure — source complete, admission-gated execution Schema 2 adds independently accepted policy and release-consumer bundles to the native run adapter. A repository job, an in-repository manifest, a workflow hash, an environment flag, or a status row cannot establish this execution boundary. The shipped source is preparatory tooling; it installs nothing and provides no production pin, key, runtime image, controller service or live-run attestation. No repository flag enables publication: the repository publication entrypoints always refuse, and execution happens only through a separately admitted operator run on the independently installed controller. Admission is dynamic by design: an admitted run holds its credentials only for that run, closes its ledger record and removes credentials afterwards, and between runs admission is closed. The boundary is fail-closed admission — neither a permanently disabled nor a permanently enabled execution switch. The 2026-10-15 accepted publication is live proof that this path ran: verified at the origin and edge by the authenticated publisher, rollback data retained, the rollback path not exercised in that run, and admission closed with no pending records. The earlier schema/quota BLOCK findings have source corrections; their accepted evidence does not attest another candidate or an operational controller. Any S1 BLOCK or incomplete engine scan still fails the policy stage, never becoming a REPORT-only waiver. ## Independent bootstrap root and installation prerequisite Root must independently review, acquire and install the exact bootstrap bytes (`scripts/forgejo_closure.py`) at `/opt/oss-oo/trust/bootstrap.py`, using a separate protected controller identity and immutable host/interpreter installation. Record the accepted source repository/commit, bootstrap SHA-256, host/runtime provenance and service launch command **outside the candidate repository** before first execution. The fixed service command invokes that installed file with `python3 -I -B`, a fixed `/etc/oss-oo/trust/controller.json`, and an authenticated operator/scheduler request. The immutable host/kernel, `/usr/bin/podman`, Python stdlib, external config and isolated controller account are the explicit trust root. Bootstrap cannot verify its own execution into trust; copying a candidate script and running its self-hash is not this installation procedure. No existing repository-loaded workflow satisfies this boundary. Arbitrary earlier or later steps can request repository secrets or abuse a runner with privileged host access. The preparatory `static-publication.yml` therefore has no checkout, secret expressions or release execution and always refuses. Its former bytes are retained as a passive fixture. **Root must remove privileged credentials and privileged runner access from candidate workflow scope before activation.** OFF alone does not make already available secrets safe. Native CI remains a signal with its existing required context; no stronger platform merge guarantee is claimed. Separate controller installation/secret migration is an architectural dependency, not something this source change silently performs. ## Exact pins and manifests Schema-2 pins retain all seven C0 fields (`schema`, `repository`, `workflow_path`, `workflow_sha256`, `events`, `job_name`, `required_context`) and require exactly two additional fields: `policy_bundle` and `consumer_bundle`. Each identity has exactly `repository`, `source_commit`, `manifest_sha256`, `runtime_sha256`. Commits are full lowercase 40-hex; digests are lowercase 64-hex. Unknown or missing fields, bool-as-version, schema downgrade and a bundle source equal to the candidate are refusals. The same-candidate rule means reviewed policy promotion precedes the subsequent application candidate; it cannot install its own pin. Runtime identity is the **OCI image config/content ID**, without `sha256:`. The config's ordered rootfs diff IDs bind the image layers. Root accepts its provenance independently; the controller selects `sha256:` with `--pull=never`, checks the local image ID, creates the container and checks the actual container image ID before starting it. A candidate-supplied observation is never a pin. Tags, `apt`, downloads, mutable image resolution and network package installation cannot extend the trusted runtime. Image publication/acquisition is a Root gate. Manifest keys are exactly `schema` (integer 2), `role` (`policy` or `consumer`), `repository`, `source_commit`, `entrypoints`, `files`. Required entrypoint keys: | Role | Keys | Current entrypoint | | --- | --- | --- | | policy | `sast`, `tests`, `build` | `scripts/closure-policy.py` | | consumer | `native`, `publication`, `recovery`, `commons`, `verify` | `scripts/closure-consumer.py` | All entrypoints must be present Python files. Files are sorted by exact relative path, each with exactly `path`, Git mode (`100644` or `100755`), and SHA-256. Paths use an ASCII subset, reject absolute paths, dot/empty segments, trailing dots, backslashes and case aliases. Files must be regular single-link files; directories must have mode 0755 and correspond exactly to file ancestors. Symlinks, hardlinks, devices, omitted/extra files, empty extra directories, ambiguous modes and duplicate/unsorted entries refuse before execution. There is one canonical byte representation for JSON used by this controller: `json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=True, allow_nan=False)` encoded as ASCII plus **one LF**. The decoder requires those exact bytes, rejecting duplicate keys, BOM, alternate spacing/encoding and non-finite values. Manifest digest is over those canonical bytes, stored next to the bundle tree, never inside its own recursive inventory. The builder deliberately exports the **entire exact reviewed Git source tree**, not a selective list of guessed imports. It uses `ls-tree`/`cat-file` against a full commit, no checkout, archive filters, working-tree files or candidate code. All imports, helpers, test fixtures, rules, exception inventory, coverage policy, build scripts and consumers are thereby included. Root separately verifies the claimed canonical repository/commit; a local builder cannot attest remote origin. All non-source libraries/tools belong to the independently pinned runtime image. For each role, call the independently installed builder with `bundle --source --commit --repository --role --entrypoints --destination --runtime `. It returns candidate identity values for independent review. It never writes an external pin. A manifest or matching pin carried by an application candidate is powerless. ## Controller inputs and ordering The external canonical controller config is schema **3**, with exactly `schema`, `pins`, `policy`, `consumer`, `source_repository`, `credentials`, `attestation_key`, `workspace`. The fixed `pins` table has exactly `production` and `review`. Both pins remain schema 2 and bind the same independent bundles, runtime identities, repository, workflow path/hash and job. Production binds only `push` and its required context; review binds only `pull_request` and its required context. Neither a request nor a candidate can supply a pin or change an event/context. All path fields are absolute and controller-owned; `policy`/`consumer` contain `manifest.json` and `tree/`. Root installs the config atomically, outside candidate write scope. `attestation_key` is a regular 0600 file with at least 32 random bytes. This block creates/reads no real key: tests use an explicitly synthetic constant in private fixtures. `credentials` is the one fixed protected active-credentials JSON string map. The operating package selects a Root-owned mode-specific template and materializes only that profile at the fixed path. The bootstrap rejects missing or extra keys before any native/container action. It supplies authorized scheduler context (repository, protected ref, candidate SHA, globally unique controller run id/attempt) plus exactly the selected target's credential keys. Verify has only a repository read token and no production credentials. These are **controller scheduler inputs**, not inherited CI environment claims. The request's selected mode is translated to the existing dispatch context by the pinned consumer. The native CI identity stays separate in the proof. Root must provide a durable unique run allocator, authenticated dispatch, protected audit/receipt storage and one controller host. Publication and recovery share `.controller.lock`; overlapping local requests fail busy. Multiple hosts are not supported by a local flock and must not be activated. `source_repository` is a separately hydrated **credential-free** local Git object store owned by the controller. It must contain the candidate, installed Commons release and predecessor commits needed for data-only compatibility/history reads. No candidate `.git`, hooks, config, credentials, alternates or filters are copied into it. Hydration is an external read-only acquisition step, not a consumer fetch with privileged credentials. Missing objects refuse; no artifact bypass or historical raw-SHA recovery route is added. The controller performs this actual sequence: 1. Validate config schema 3, the two whole schema-2 external pins, the exact mode/target/credential profile and protected request identity, both manifests and trees, and both pinned images, before any bundle entrypoint/import. 2. Acquire the publication/recovery lock and export exact candidate blobs into a private tree. Launch the verified consumer's `native` entry in its image to read protected main and the full C0 native run proof (all pages and re-read). Recovery first reads/validates the open ledger entry and requires its commit equal the protected scheduler dispatch SHA, then checks fresh main and native CI. Failed sync, stale/foreign/closed records or missing preflight data stop before SAST/tests/build and without any endpoint call. 3. Select application data and trusted policy files into one complete input snapshot. Fixed host `/usr/bin/git`, with no hooks, templates or system/user configuration, creates neutral metadata for fixture clones; its exported committed tree must match the selected bytes/modes exactly. Inventory this whole snapshot including that metadata. Run data-only SAST, the complete fixed test commands, and each of three builds in a **fresh container**. Remove each container and all its processes before starting the next. Every command must exit zero. No key or credential is mounted. Application-running containers receive the snapshot, bundle and raw candidate only as read-only binds; they do not receive the controller's source Git object store. 4. Re-read protected main/native provenance and require identical native records. Recovery repeats its read-only preflight and requires the same record number and canonical complete payload digest; drift cannot reach the final consumer. All application containers have already been removed. Recheck the complete input inventory and read the exact generated artifact inventory. Commons then prepares its deterministic module packet in a pinned consumer container with network disabled, a readonly candidate data tree and no credential mount; that packet joins the authenticated artifact inventory. Verify compares the same full current PR identity and native payload at the pre-build, post-build and final consumer boundaries, with current main equal to the bound base SHA. It reads no branch-protection administration route. 5. Authenticate a receipt, re-read config/key/credentials and both bundle trees, and verify output inventory again. Interrupted re-pin or rotation refuses. 6. Start the verified consumer in its pinned image with only the authenticated private channel, readonly artifacts and Git object data, protected credentials, and its own writable temporary/output space. Candidate executables are not mounted. The consumer re-verifies the HMAC and native server data before using artifacts or contacting the production endpoint. Recovery re-reads the selected record once more, requires its number/payload digest match the authenticated pre-build selection, and repeats dispatch, fresh main and native proof checks before endpoint work. No fallback selects a different record. The existing endpoint candidate/ticket/runtime binding still precedes artifact consumption and any positive close. Container flags include read-only image, ignored image volumes, no capabilities, no-new-privileges, private PID/IPC, resource/output bounds, narrow bind mounts and fixed work/temp directories. Policy stages use `--network=none`; only the pinned consumer has networking. `--unsetenv-all` plus a fixed environment clears caller `PYTHONPATH`, preload variables, tool options and credentials. Python uses `-I`; the only bootstrap-added import directory is `/bundle/scripts`. No container socket, host home or unrelated host mount is exposed to application tests. Only containers created by this invocation are force-removed on exit/failure. ## Test policy versus application input `closure-policy.json` is included in the pinned policy bundle. It fixes commands, checkers and application selection paths. Ordinary candidate input may replace `site/`, Commons migrations and explicitly named Commons application modules. The explicit module list includes provider-neutral actors. Identity-v1 schema, preparer and manifest, the fixed Commons schema-profile JSON, and all identity test/support files remain pinned policy inputs. The fixed Commons and UI commands include their complete foundation, activity and growth regression modules. Changes to scripts, test files/support or other service policy require independent policy re-pin; replacement is rejected, not silently selected. The host prepares the selected snapshot before starting any application. The same-user writable copy formerly used for test execution is removed. Actual checks, support code, selected application bytes and neutral Git metadata stay behind read-only mount boundaries throughout application execution. Chmod is not that boundary; the entrypoint also refuses a writable input mount. A candidate process cannot reach a writable alias of those files. The trusted host and kernel remain prerequisites. Each command receives fresh writable `/work`, `/tmp` and `/output` only. Ordinary Git fixtures clone into temporary space. `check-site.sh` accepts the explicit `SITE_CHECK_OUTPUT` scratch path while retaining `dist` as its local default. Builds write `/output/tree`; the host removes the container before moving that tree into final artifacts. Neither a later test nor a later build can access a previous command's scratch, output or process namespace. Application tests necessarily execute hostile program behavior; a program can misbehave within a test process. Their logs are not an independent trust source. The outer pinned orchestrator selects and records the complete fixed command list and process exit results. This proves file/execution identity and isolation from controller credentials; it does **not** prove an independently observed semantic test verdict against arbitrary hostile application imports sharing the unit-test interpreter. Such imports can affect that process's assertions or exit. Fresh containers prevent that process from changing a later command's inputs or surviving into its process namespace. SAST separately parses raw candidate blobs with the pinned S1 runner/rules/engine, never importing candidate helpers to decide coverage or success. SAST findings are not inferred from a candidate-supplied receipt. New native `.oss-oo`/`.oss` workflow discovery still requires the separate S1 coverage update before that platform feature rolls out. `CI=true` makes mandatory runtime tools fail rather than silently skip. The policy image needs Python 3.12, Git, Node 24, pinned OpenGrep at `/opt/oss-oo/bin/opengrep`, PyYAML, unshare support, Foundry and the exact offline solc/npm cache. Npm is forced offline with its cache inside the immutable image. OpenGrep's pinned onefile launcher expands files larger than the unchanged 32 MiB file limit. The policy explicitly selects an immutable pre-expanded cache using `sast_cache` in the protected policy JSON: root and manifest paths plus manifest SHA-256 `671042acd4f5135d4c77536e54227685d16368ede5da2da41dfbbf5d48c606c8`. The manifest lives outside the cache tree and binds all 102 files (256775858 bytes), modes and hashes, with exactly their implicit 0755 directories. S1 verifies that complete read-only inventory before every engine child. The original engine launcher SHA remains required. Only explicit CLI options select this cache; ambient `XDG_CACHE_HOME` and adjacent manifests grant no trust. The local S1 default remains cache-free. Its fixed test command carries the same explicit options and executes every existing case, including a separate default check. The consumer image needs Python 3.12, Git, SSH and CA roots. Locally prepared images are test inputs, pending independent image/provenance acceptance. A missing tool, unavailable nested namespace, S1 BLOCK or incomplete engine scan therefore refuses. The tested cached Python fixture image is **not** offered as a production policy image. ## Native proof and protected consumption The adapter keeps C0-R1's triggering commit versus workflow source distinction, internal run id versus display index, complete pagination, newest-run rule and single-run re-read. Schema 2 also binds positive job attempts and rejects duplicate job ids. `verify_native` alone never authorizes a protected consumer. The private receipt schema is **3**, with production kind `forge-execution-hmac` and MAC prefix `oss-oo/execution/v3` followed by one NUL. Read-only review uses kind `forge-review-hmac` and a separate prefix `oss-oo/review/v1` followed by one NUL. There is no receipt-schema downgrade or unprefixed MAC fallback. Production plan, publication, recovery and Commons consumers all reject a review receipt. This is HMAC-SHA256 with Python's standard library, **not a public-signature attestation**. It binds the canonical whole pin digest, complete native record (repository, candidate, event, source, run/jobs/attempts and status linkage), both accepted bundle identities, fixed successful stage identities/transcript hashes and exact artifact inventory. It also binds the full prepared-input inventory digest and every command's index, exact argument list, timezone, zero exit code and transcript hash in order; the stage hash covers its whole command sequence. SAST has one command and build has three. The selected input digest is execution identity, not an application-supplied claim. Policy bundle identity independently binds the required command list. For recovery it additionally binds the pre-build ledger record number and full canonical payload digest (`recovery_record`); other modes carry JSON null. The controller and pinned consumer share the independently protected key. No candidate can supply that key or use an in-repository receipt as authority. The whole typed request additionally binds candidate, mode, target, credential profile, immutable repository ID, protected scheduler run ID/attempt and the exact PR number/head/base refs and SHAs for verify. Review also binds hashes of the complete current PR object and native event payload. A review result grants no static or Commons publication gate; it must not be reused after merge. The mode/key contracts, native Commons adapter, separate append-only ledger and remaining operator gates are specified in [the native controller contract](native-commons-controller.md). `forgejo_trust.verify` accepts legacy schema 1 only for its explicitly staged transport interface; `Forge.trusted_ci` requires schema 2 and a real `Prepared` handoff. `forgejo_publication.run` and `forgejo_recovery.run` refuse a missing handoff before ledger/endpoint work. Their artifact path imports only the pinned consumer's checker, validates the three prebuilt trees/descriptor, and never invokes candidate build scripts or release helpers. Legacy local build functions remain fixture helpers, unreachable from protected entries. Publication retains fresh native/main checks at its existing transition boundaries and records full schema-2 provenance. Recovery retains E5's exact recorded candidate == dispatch == fresh main binding, endpoint candidate/ticket/runtime binding, fail-closed ledger sync, loss/rollback rules and predecessor data reads. The final recovery entry performs one native trust read before endpoint work. The new controller separately repeats its record/main/native preflight after the isolated build as documented above; this is added by C0-B, not attributed to the unchanged E5 engine. Older records retain the evidence-bound manual operator path. ## Activation evidence requirements (defined before the first admitted run) This section records the evidence requirements as written before the first admitted live run. The 2026-10-15 accepted publication has since demonstrated the closure path end-to-end on the current infrastructure; the per-run admission, custody and reconciliation gates below remain the specification for every future run. Independent source review and final integration precede Root's acceptance of new source/manifest/runtime identities. UI/identity changes may alter build/check/test inventories; rebuild the **full resulting** bundles after their integration. Root must separately prove the immutable bootstrap/host/runtime installation and credential withdrawal from candidate runners, scheduler/run allocation, local serialization, source hydration and protected evidence storage. Then obtain real native push execution proof (and deployed target-source evidence before enabling target events), bind a complete passing S1 run to that exact candidate, and satisfy the unchanged release, backup/restore, OFF-to-ON, endpoint, HTTP, ledger and rollback gates. Local tests include hostile manifests, checker/import replacement, omitted dependencies, extras/aliases/symlinks/modes, joint candidate pin changes, runtime mismatch, downgrade, native identity changes and pre-execution sentinels. `python3 scripts/test-forgejo-closure.py --podman-smoke` executes a real isolated fixture with only a cached image and synthetic key/credentials, tests import/env shadowing and attempted credential/network access, then authenticates and consumes fixture artifacts. Its native server and website policy bodies are fixture code; it is real container isolation evidence, not a live native/SAST/release verdict. `--readonly-smoke --fixture-runtime ` additionally checks real write/chmod/unlink/rename refusal for protected inputs, fresh scratch/output, ordinary repository checks, Git fixtures and three deterministic builds. It neither changes a production runtime pin nor creates a publication approval.