Evaluation beta preflight¶
tools/evaluation_beta_bootstrap.py preflight collects fail-closed evidence
for the disposable, non-required evaluation beta. It reads a reviewed source
checkout, GitHub.com, one running service, and PostgreSQL. It then writes one
sanitized JSON report.
The preflight is an inspection tool, not the beta itself. It does not submit a review, send a webhook, publish a Check Run, or change repository rules.
The command targets GitHub.com with REST API version 2026-03-10. GitHub
Enterprise Server is not supported.
See Preflight a disposable evaluation beta for the operator procedure.
Command¶
Run the bootstrap from the reviewed Extra CODEOWNERS source root. Set
UV_PROJECT_ENVIRONMENT to a directory outside that checkout before
bootstrapping or running the command:
python -I -S -B tools/evaluation_beta_bootstrap.py preflight \
[--config PATH] [--report PATH]
Use uv run --no-sync to run that command from the already-bootstrapped
external environment. The launcher refuses to run without isolated, no-site,
and no-bytecode interpreter modes. Before importing third-party or checkout
code, it uses fixed /usr/bin/git commands to reject tracked, untracked, and
ignored changes, index flags that can hide changes, and replacement refs. It
reads the owner-only configuration outside the checkout with the standard
library, then requires this checkout's HEAD, index, and raw tracked bytes to
match the configured source_revision before importing it. It appends the
reviewed checkout and then the external environment after the standard
library, so an external package cannot replace project or preflight code. It
does not process .pth files or site customization. The source probe then
verifies the commit signature and repeats the signed-tree comparison.
| Option | Default | Meaning |
|---|---|---|
--config PATH |
EXTRA_CODEOWNERS_BETA_CONFIG_FILE, then evaluation-beta-preflight.toml |
Owner-only, non-secret TOML configuration outside source_checkout. |
--report PATH |
EXTRA_CODEOWNERS_BETA_REPORT_FILE, then evaluation-beta-preflight-report.json |
New JSON report path. The parent directory must already exist. |
An explicit option takes precedence over its environment-variable default.
Secret inputs¶
The TOML file contains no credentials. Four environment variables supply the secret inputs:
| Variable | Required value | Use |
|---|---|---|
EXTRA_CODEOWNERS_BETA_CHECKER_PRIVATE_KEY_FILE |
Path to the checker App's PEM private key | Signs a GitHub App JSON Web Token (JWT). |
EXTRA_CODEOWNERS_BETA_APPROVER_PRIVATE_KEY_FILE |
Path to a different App's PEM private key | Signs the approver App's JWT. |
EXTRA_CODEOWNERS_BETA_OPERATOR_TOKEN_FILE |
Path to a dedicated fine-grained personal access token (PAT) beginning with github_pat_ |
Reads Actions permissions and classic branch protection for the target repository. |
EXTRA_CODEOWNERS_BETA_DATABASE_URL |
A postgresql+psycopg URL with one explicit host, database, username, and nonempty password |
Opens a bounded, read-only database probe. |
The preflight never reads GH_TOKEN or GITHUB_TOKEN. It rejects an operator
PAT that is also present in either variable.
Local input files have these requirements:
| File | Size | Ownership and mode |
|---|---|---|
| Configuration | 1 byte to 64 KiB | Current user, one hard link, regular file, exact mode 0400 or 0600 |
| Each App private key | 1 byte to 64 KiB | Current user, one hard link, regular file, exact mode 0600 |
| Operator PAT | 1 byte to 4 KiB | Current user, one hard link, regular file, exact mode 0400 or 0600 |
| SSH allowed-signers file | 1 byte to 64 KiB | Current user, one hard link, regular file, not writable by group or other users |
Final-component symbolic links and non-regular files are rejected. Each file is read through an open descriptor and checked again for changes. The operator PAT must be printable ASCII on one line; one final line feed is accepted and removed.
Configuration file¶
The configuration is strict TOML. Unknown fields and values of the wrong type are errors. The checked-in example contains every field and no secrets.
| Field | Required | Constraint and meaning |
|---|---|---|
schema_version |
No | Integer 1; defaults to 1. A Boolean is not an integer here. |
source_revision |
Yes | Full 40- or 64-character hexadecimal commit ID expected at checkout HEAD; normalized to lowercase. |
source_signer_fingerprint |
Yes | Full 40- or 64-character OpenPGP fingerprint, normalized to uppercase, or an exact case-sensitive SSH SHA256: fingerprint with 43 base64 characters. |
source_ssh_allowed_signers_file |
For SSH signatures | SSH public-key trust file. A relative path is based at the configuration file's directory. Required for an SSH fingerprint and forbidden for an OpenPGP fingerprint. |
source_checkout |
Yes | Reviewed source directory with no tracked, untracked, or ignored changes. A relative path is based at the configuration file's directory. The bootstrap requires this explicit path so it can bind the configured revision before importing checkout code. |
python_version |
Yes | Exact running Python version, such as 3.12.7. |
uv_version |
Yes | Exact version parsed from uv --version. |
extra_codeowners_version |
Yes | Exact imported package version. The package must load from source_checkout. |
postgres_server_version_num |
Yes | Positive five- or six-digit integer returned by PostgreSQL SHOW server_version_num. |
organization_id |
Yes | Positive numeric ID of the disposable GitHub organization. |
target_repository |
Yes | Public owner/repository used for beta pull requests. It cannot be .github. |
target_repository_id |
Yes | Positive numeric ID of the target repository. |
organization_policy_repository |
Yes | Public owner/.github repository under the same owner as the target. |
organization_policy_repository_id |
Yes | Positive numeric ID of the .github repository. It must differ from target_repository_id. |
target_default_branch |
Yes | Exact target default branch. Unsafe or ambiguous Git ref names are rejected. |
target_default_branch_sha |
Yes | Full 40-character commit ID pinned for the target default branch. |
organization_policy_default_branch |
Yes | Exact .github default branch, with the same branch-name restrictions. |
organization_policy_default_branch_sha |
Yes | Full 40-character commit ID pinned for the .github default branch. |
checker_app_id |
Yes | Positive numeric ID of the Extra CODEOWNERS checker App. |
checker_app_slug |
Yes | Lowercase checker slug containing letters, digits, and internal hyphens. |
checker_installation_id |
Yes | Positive checker installation ID. |
approver_app_id |
Yes | Positive numeric ID of the App that submits the delegated review. |
approver_app_slug |
Yes | Lowercase approver slug containing letters, digits, and internal hyphens. |
approver_installation_id |
Yes | Positive approver installation ID. |
approver_bot_user_id |
Yes | Positive numeric ID of the approver App's bot account. |
service_url |
Yes | One HTTPS origin with no credentials, path, query, or fragment. A final slash is removed. |
checker_webhook_url |
Yes | The exact service_url origin followed by /webhooks/github. |
check_name |
No | Check context that must remain non-required; defaults to Extra CODEOWNERS / approval. Length is 1 to 100 characters; C0 controls are rejected. |
policy_path |
No | Literal relative path used in both repositories; defaults to .github/extra-codeowners.toml. |
delegation_test_path |
Yes | Existing harmless file below docs/. The beta delegation must name this exact path, not a glob. |
delegation_test_labels |
Yes | One to ten unique labels that the one beta delegation must require together. Labels are normalized to lowercase. |
Tool-version strings contain at most 64 letters, digits, ., +, _, or -
characters and start with a letter or digit. Repository names are normalized
to lowercase. Policy paths contain only ASCII letters, digits, _, ., and
- in slash-separated segments. They cannot contain empty, . or ..
segments and cannot exceed 255 characters.
The target and .github repositories must share one organization. Repository
IDs, App IDs, App slugs, and installation IDs must identify distinct objects
where their roles differ. checker_webhook_url and service_url must share
the exact origin. Explicit URL and PostgreSQL authority ports must be between
1 and 65535; an omitted PostgreSQL port means 5432.
For SSH signatures, the preflight ignores Git's global configuration. It passes the configured allowed-signers descriptor to Git explicitly and rejects a trust file that changes during either signature check.
GitHub App contracts¶
The preflight requires exact App and installation permissions. Extra permissions fail the check; missing permissions do too.
| App | Exact permissions | Exact subscribed events |
|---|---|---|
| Checker | Checks read/write; Contents read; Pull requests read; Commit statuses read/write; Metadata read; organization Members read | Check run, Installation target, Label, Member, Membership, Organization, Pull request, Pull request review, Push, Repository, Team, Team add |
| Approver | Contents read; Pull requests read/write; Metadata read | None |
Both Apps must be owned by the configured organization. Each App must have exactly one current installation and no pending installation requests. The installations must be active, use Only select repositories, and contain these exact sets:
| Installation | Exact repositories |
|---|---|
| Checker | Target and organization-policy .github repository |
| Approver | Target repository |
The checker hook must use checker_webhook_url, JSON content, TLS certificate
verification, and a nonempty secret. The approver bot account must have the
exact configured numeric ID and <approver-slug>[bot] login.
GitHub's API does not expose the App registration choice Only on this account or whether the configured checker hook is active. Those remain manual prerequisites.
Checks¶
A normal report contains all 11 checks. A failure in one check does not skip the other independent checks.
| Check ID | Passing evidence |
|---|---|
source |
Fixed /usr/bin/git sees a current-user-owned, non-shared-writable, standalone, non-shallow checkout without alternates, replacement refs, linked-worktree metadata, gitlinks, or tracked, untracked, or ignored changes. HEAD is the pinned commit, strict fsck succeeds, and show plus verify-commit report a good signature from the exact fingerprint. Each of two observations requires safe index flags and modes, exact index equality with the signed commit tree, no ignored content, and matching worktree Git blob hashes. Evidence records the tracked-file count, byte count, untracked_and_ignored_content: absent-at-both-observations, and tracked_content: hashed-twice-against-signed-tree. |
tool_versions |
Python, uv, and Extra CODEOWNERS versions match. Python imported the package from the configured checkout. |
app_installations |
Both keys match their exact App identities, permissions, events, organization ownership, single-installation inventories, and repository selections. No installation request is pending. The checker hook configuration and approver bot identity match. Each short-lived probe token contains only Metadata read. |
public_repositories |
Both configured repositories are public, available, organization-owned objects with the expected numeric IDs, full names, default branches, and branch commits. |
branch_safety |
Active rules or classic protection require native code-owner review and at least one approving review. The configured Extra CODEOWNERS context is not required, and no merge queue is active. |
codeowners |
GitHub reports no errors for .github/CODEOWNERS at the pinned target commit. The bounded UTF-8 file parses locally, contains a rule, and owns the test path. |
policy |
The pinned repository and organization policy files parse and compile with built-in non-delegable paths enabled. Organization policy enrolls exactly the configured approver and no other App. Repository policy contains one delegation and no other delegation; it names the exact test path, exact CODEOWNER set, exact App, exact required-label set, and no forbidden labels. Each configured label must be present for eligibility. |
service_health |
/, /api/runtime-identity, /health/live, and /health/ready match a production source deployment of the configured checker. The service reports PostgreSQL, the exact policy and check settings, enabled and healthy worker and reconciler tasks, and a recent successful GitHub authentication as the configured App ID. A source deployment must report build_revision: null. |
postgresql |
The URL uses the exact postgresql+psycopg driver and one explicit host, database, username, and nonempty password. The connection enforces read-only transactions, search_path=public, and five-second statement, lock, and idle-transaction timeouts. The server version, Alembic head, and required-release-contract match the running code. Evidence reports schema_contract: required-release-contract. |
insecure_changes_metric |
/metrics contains exactly one unlabelled extra_codeowners_insecure_changes_enabled sample with value 0. |
final_branch_refs |
Both repository identities, availability states, default branches, and branch commits still match after the other checks finish. |
The signed source tree must contain 1 to 10,000 unique, stage-0 regular blobs
with mode 100644 or 100755. Paths may be at most 4,096 bytes and use only
ASCII letters, digits, _, ., -, and slash-separated safe segments. Every
worktree file must be a current-user-owned, single-link, non-group- or
world-writable regular file no larger than 64 MiB. Its executable state must
match the signed mode. The total tracked content cannot exceed 512 MiB.
Every untracked path is rejected, whether or not Git ignore rules hide it. Keep virtual environments, build output, editor state, and operator inputs outside the checkout. This prevents an ignored module in the source root from shadowing a reviewed dependency while still producing a passing report.
At both observations, every index tag must be exact uppercase H; this
rejects assume-unchanged, skip-worktree, and fsmonitor-valid state. The
preflight opens each path through checkout-anchored, no-follow descriptors,
requires stable file and path metadata while reading, and hashes the Git blob
with the repository's SHA-1 or SHA-256 object format. The result must match the
object ID in the signed tree.
The source check requires Linux facilities including /proc/self/fd,
O_NOFOLLOW, and /usr/bin/git. Git commands run with bounded output and
time, a small environment, disabled hooks and prompts, and no system or global
Git configuration.
Remote PostgreSQL connections require sslmode=verify-full. A direct loopback
address or operator-controlled Unix-socket proxy may omit TLS. An authority
port, when present, must be between 1 and 65535; omission means 5432. An
optional hostaddr requires an explicit host and verify-full.
sslrootcert, when present, must name a nonempty absolute path. The connection
sets gssencmode=disable, preventing GSSAPI encryption from bypassing the
pinned TLS certificate path. Service-file routing, unknown query parameters,
ambient libpq connection variables, hostless and comma-separated routes, an
authority host combined with a query-string host, and caller-supplied libpq
options are rejected. The URL must carry its own password, so .pgpass and
PGPASSFILE are not used.
GitHub and service requests time out after 10 seconds, do not follow redirects, and ignore ambient proxy and certificate environment variables. Responses, remote files, command output, configuration, keys, metrics, and the report all have fixed size limits.
Report¶
The report writer creates a new mode-0600 file and refuses to replace any
existing path. Its parent must already exist, belong to the current user, and
not be writable by group or other users. The destination cannot be inside the
source checkout or collide with the configuration, key, PAT, or SSH trust
file.
Publication uses an exclusive temporary file, verifies the published inode and metadata, synchronizes the parent directory, and removes partial output when post-publication verification fails. The encoded report cannot exceed 128 KiB.
A completed evaluation has these top-level fields:
| Field | Meaning |
|---|---|
schema_version |
Exact report schema; currently 1. |
kind |
extra-codeowners-disposable-evaluation-beta-preflight. |
run_nonce |
Random 32-character hexadecimal identifier for this invocation. |
captured_at |
UTC report-construction time in ISO 8601 format, written after the checks. |
result |
passed when every check passed; otherwise failed. |
preflight_passed |
Boolean result. Consumers must require literal true. |
source_revision |
Configured source commit. |
scope |
Repository, App, hook, policy, test-fixture, and source-deployment identity. |
checks |
Ordered array of 11 check results. |
limitations |
Facts that the preflight did not establish. |
Each check result contains id and outcome. A pass adds a bounded
check-specific evidence object. A failure adds one sanitized failure
string. Control characters are escaped, and failure text is limited to 300
characters.
A configuration failure writes a smaller report with
result: "configuration_error", preflight_passed: false, and a sanitized
field-and-error-type summary when the report destination is safe. A setup
failure writes result: "failed" and a sanitized exception class. An absent
checks array means that evaluation never ran.
The report excludes keys, installation tokens, the operator PAT, the database URL, authorization headers, source policy contents, raw provider responses, and webhook payloads. It is sanitized, not anonymous. Repository names, App identities, installation IDs, versions, timestamps, paths, and counts remain visible.
Exit status¶
| Status | Meaning |
|---|---|
0 |
Configuration loaded, all 11 checks passed, and the report was created. |
1 |
Evaluation or setup failed, or an evaluated report could not be created. |
2 |
Command-line or configuration input was invalid. A configuration-error report is created when possible. |
Terminal output only identifies the report and gives a short failure direction. The JSON report is the evidence record.
Read and write boundary¶
The preflight reads:
- local source, Git metadata, tools, and trust files
- public repository metadata, refs, active rules, labels, files, and
CODEOWNERSerrors - the two App registrations, installation inventories, and installation requests
- checker hook configuration and classic branch protection
- service identity, liveness, readiness, and metrics
- PostgreSQL version and schema metadata.
It creates one local report. PostgreSQL connections use read-only
transactions. The only GitHub POST requests mint short-lived,
Metadata-read installation tokens; the tokens remain in memory. The command
does not create or modify a repository, rule, pull request, review, Check Run,
file, App, installation, or database object.
What a pass does not prove¶
A pass records sequential observations, not one atomic snapshot. It does not prove:
- that the running source process loaded
source_revision; runtime identity is an unauthenticated self-report, and source deployments deliberately reportbuild_revision: null - that the database URL reaches the same PostgreSQL instance as the service
- the operator PAT's exact repository selection or absence of write permissions; GitHub offers no token-introspection endpoint
- the Apps' Only on this account setting
- that the checker hook is active, that the service and App share the same webhook secret, or that a delivery reaches the service
- branch-rule bypass actors, administrator enforcement, or unrelated rule settings outside the properties checked above
- behavior after the starting snapshot, including a real App review, Check Run source, stale-review invalidation, human review, missed delivery, reconciliation, shared-head handling, or merge blocking
- independent authority from labels. Pull requests write access lets the approver App change labels, so labels route the workflow while path, CODEOWNER, App identity, and non-delegable rules limit authority
- production, release, or hosted-service readiness.
Native code-owner enforcement must remain enabled throughout the beta. The manual acceptance and cleanup record lives in issue #76.