A38 is the versioned process for documenting a complete local CI run on a pull request. This document and the guard guide are the central process definition. An adopting repository owns only its .github/a38.json manifest, its guard integration, and a contributing-guide pointer to this standard; it does not copy these process instructions or need its own docs/a38.md. Tool plugins and workflow skills likewise point here instead of defining another A38 process.
The PR author processes every required local job and posts the generated report as a PR comment. When every changed path ends with .md (case-sensitive) and the inventory is complete and trustworthy, the local author suite is skipped entirely: every required job is recorded as result=not_applicable with markdown_only: true, and the guard waives the author-report gate after independently confirming the GitHub file list. When every changed path is a markdown file and/or exactly .github/workflows/a38-guard.yml (guard-docs) under the same fail-closed inventory rules, the local suite is likewise skipped without markdown_only: true; the guard waives the author-report gate with reason guard-docs change set and does not fail that guard workflow file's bytes-changed policy line. Other workflow paths still require maintainer policy approval. That full skip is broader than optional readme_only.omit_jobs, which remains a subset omission for README.md-only change sets when that policy object is configured. The dfx pr guard explains those rules and verifies the report against the repository's trusted policy.
The policy schema is a38/v1. The report remains the frozen dfx-local-ci/v1 format: A38 does not introduce a second report payload. This standard can be adopted by private or public repositories; it does not automatically replace any existing required checks. This public client retains its GitHub Actions gate.
The example manifest is illustrative: replace its job and workflow names with the target repository's actual inventory. Every key shown is required; unknown keys and duplicate JSON keys are invalid. The only additional allowed optional object is readme_only (see below); repositories without it keep the full required job set on every run.
| Field | Meaning |
|---|---|
schema |
Exactly a38/v1. |
standard |
Exactly A38. |
documentation |
Exactly docs/a38.md. This frozen schema token identifies the central A38 standard; it does not require that path in the adopting repository. |
mode |
observe for advisory rollout or enforce for a report gate. |
jobs |
Nonempty complete list of required local jobs. |
exclusions |
Explicit workflow jobs outside the local test contract, with reasons; may be empty. |
readme_only (optional) |
Object with only omit_jobs: a nonempty list of unique job ids that already appear in jobs. When every changed path is exactly README.md or ends with /README.md (case-sensitive) and the inventory is complete and trustworthy, those jobs are eligible for subset omission as result=not_applicable with exit_code=0 and log text omitted: README-only change set when the broader markdown-only full skip does not already omit every required job. Empty change sets, unknown statuses, renames involving a non-README path, truncated inventories, more than 500 files, or git errors keep the full suite. Omitted jobs are never recorded as pass. |
Separately from that optional policy object, when every changed path ends with .md (case-sensitive) and the same fail-closed inventory rules hold, A38 omits all required jobs as not_applicable with log text omitted: markdown-only change set and report flag markdown_only: true. No author report is required for Ready when the guard independently confirms that markdown-only inventory. When every changed path is a markdown file and/or exactly .github/workflows/a38-guard.yml and those rules hold, A38 omits all required jobs as not_applicable with log text omitted: guard-docs change set and does not set markdown_only: true. Keep the markdown-only flag and log when the change set is purely .md. No author report is required for Ready when the guard independently confirms that guard-docs inventory. Changed bytes of .github/workflows/a38-guard.yml in a guard-docs change set do not require maintainer policy approval; any other workflow path still does. A pure README.md change set is both README-only and markdown-only; the full markdown skip applies and readme_only: true is set only when configured omit_jobs also matched.
Each required job contains id, name, timeout_s, workflow, and job, plus exactly one input: either command or executor. An optional lock may appear on the command form (null or a nonempty name of ASCII letters, digits, ., _, or -). Executor jobs set lock in executor.config, not as a sibling of executor. The legacy command form remains an exact local shell command and is preserved unchanged. An executor contains exactly adapter and config; adapter is one of commands, compose, http-smoke, or immutable, and its object-valued config is validated by that adapter's pure, strict parser. For example:
{
"executor": {
"adapter": "commands",
"config": {"steps": [["echo", "example"]]}
}
}Central policy loading normalizes this structured form to an internal job with that same command string plus the resolved lock (null when none): agent a38 job ADAPTER --config followed by shell-quoted, compact, sorted-key, ASCII JSON. Non-finite values are rejected. Execution, the frozen command-only report schema, report verification, and Guard comparison all use that same deterministic command string. The A38 job adapters document each adapter's configuration contract.
IDs are unique, and timeouts are finite positive seconds up to 86400. Workflow paths identify .github/workflows/*.yml or *.yaml; job is the workflow's job identifier. An exclusion contains only workflow, job, and a meaningful reason. A workflow/job pair appears exactly once across required jobs and exclusions.
Inventory every workflow job, including jobs in reusable workflows. A command representing a matrix job must exercise all required variants; one successful variant is insufficient. Local commands must include the preparation, services and assertions needed to reproduce the corresponding CI checks. Merely starting services or skipping an unavailable dependency does not satisfy a test. Deployment, publication and maintenance jobs need explicit exclusions; do not run production mutations as local tests. Document any GitHub-specific validation and how its separate gate is enforced.
Optional repository scope is configured separately in .github/pr-guard.json (pr-guard/v1): explicit enforce / exclude target-branch lists and an enforce|exclude default. The Agent reads that file from an immutable commit of the repository default branch (metadata from the PR base repository only) and evaluates exact case-sensitive branch names. Missing configuration retains legacy enforce-all for every in-scope target; malformed or denied configuration fails closed. Exact target main is out of scope when it is not the repository default branch (a develop→main release PR), even if a38.enforce lists main. When the default branch is main, it is the integration branch: pr-guard.json applies, and listing main in a38.enforce is valid. The default branch is only the trusted location of that file, not an implicit enforce. Excluded targets are not_applicable without loading reports or policy. See dfx pr guard for the full contract, including freshness checks and migration rules for config edits.
The guard reads the A38 manifest at the PR's immutable base SHA. A report cannot choose its own required list or timeouts. The normal active policy is immutable base policy. New unclassified jobs, changed workflow definitions and changed .github/pr-guard.json bytes fail validation unless a maintainer explicitly approves the migration for the exact current head and base. In an APPROVED GitHub review, include the declaration A38-POLICY-APPROVAL:v1 head=<HEAD_SHA> base=<BASE_SHA>. It may stay on one line or wrap at token boundaries onto immediately adjacent lines, with spaces, tabs and indentation around those boundaries and at the declaration's outer edges. The exact marker, head= and base= key names and lowercase 40-hex SHAs are not normalized; prefix prose, suffix garbage, missing or duplicate keys, split SHAs and fragments joined from separate declarations do not match. Surrounding prose remains allowed on separate lines. The reviewer must be a different person from the author and currently have write, maintain or admin permission. Dismissed, superseded or stale approvals do not count. Proposed pr-guard configuration is not activated for the current PR even after approval.
After that approval, the exact approved head manifest becomes the active policy data, but the executable guard remains pinned and the base policy's enforcement mode remains effective until merge. The full workflow inventory and report are then reassessed against the head policy. A new head or base invalidates the approval and requires a new exact approval and full reassessment.
For a new migration proposal, publish the draft first per the pull request lifecycle. Proposal measurement against the still-active base policy may follow that publication on the same signed commit; this proposal run is not Ready evidence. After exact approval, fetch the approved head policy and apply evidence reuse after approval: revalidate matching existing evidence instead of unconditionally executing the tests again. Do not use an unapproved head manifest as the active policy.
If the base manifest is missing or invalid, bootstrap requires the same independent explicit approval of the exact current head and base and is always enforce; absence is never a waiver. Publish the bootstrap proposal draft first per the pull request lifecycle; full pre-push test gates are not a draft-publication blocker. For review, the exact proposed head policy may be loaded outside the checkout and used to run the actual complete tests and produce a genuine author report, clearly marked as PROPOSED evidence; this does not activate the policy, substitute for approval, confer private A38 Ready equivalence before a valid base policy exists, or satisfy the final Ready live join. After approval, the original proposed report may qualify under evidence reuse after approval; otherwise execute the complete required run against the now-active policy. In both cases, verify the report, publish it and perform the fresh live join. Never handcraft or fabricate a report. See the guard instructions for event handling and operational limits.
Approval changes authorization; it does not by itself invalidate a completed test run. A successful report measured before approval may become Ready evidence after approval when all of the following are proven:
- The target repository, exact tested head and base are unchanged.
- The saved policy used for measurement is byte-identical to the now-active approved policy, and its required job inventory, commands and timeouts still match the complete successful report.
- Relevant recorded execution inputs, including companion repository revisions, are unchanged. The saved policy and run provenance must be available; do not infer equivalence from a green summary alone.
- The exact current policy approval remains valid, and fresh local verification and the current live guard assessment both pass.
Revalidate the original report against the approved policy, publish it through the original author's account and perform the fresh live join. Preserve the original report block, measurement timestamp, results and durations; do not make an old run appear newly executed. This also applies to a bootstrap report originally marked PROPOSED, once its exact policy has actually been approved.
If required inputs changed, equivalence cannot be established, or evidence is missing, stale, incomplete or failed, execute the complete required suite again and generate a new report. Reuse never substitutes for approval, permits unapproved policy activation, or bypasses an independently required GitHub check or human merge.
A38 supports public and private repositories. Adoption alone changes no global CI, review, repository, or merge policy.
The special local-CI equivalence applies only when the PR target is private and its trusted immutable base contains a valid A38 manifest. For those opted-in PRs, a current live-validated author A38 report deterministically satisfies the equivalent code gate by replacing duplicated GitHub code and test-suite checks for jobs covered by the active policy; missing Write access to ci or ci:full, actor gates, or action_required do not block Ready for that covered code suite. Independently required GitHub-only checks, technical GitHub merge restrictions, independent review gates, and human merge remain required. Public and default workflows and bootstrap retain their existing boundaries, and bootstrap gains no equivalence before a valid policy exists on the trusted base.
When the adopting repository also opts in to bot-owned fork workflow approval, GitHub may still hold fork pull_request runs until that same live enforce pass. The trusted guard then approves those waiting initial runs. Ready for review does not start CI. A human merger does not click Approve and run workflows. Approval is not a test result.
Independently of that opt-in, the guard may post an informational PR-GUARD:CI-MANUAL:v1 comment when a human started workflows on the current head. The guard guide owns that comment; it does not authorize auto-ready.
Public adopters, private repositories without that trusted-base opt-in, and all default workflows retain their existing cumulative GitHub CI expectations. This public Agent repository continues to require its GitHub Actions checks. In every repository, independently required GitHub-only checks remain required; A38 never bypasses technical GitHub merge restrictions. Existing independent review gates and human-merge requirements also remain in force.
Execution follows the adopting repository's orchestration and role rules. Reviewers remain read-only and do not run A38 as a review lane.
For the private opt-in path, finish the changes and any rebase first, create the repository-required signed commit, and require a clean checkout—including no untracked files—at that exact final SHA before measuring for Ready. The draft may already exist under the pull request lifecycle. Install the repository's documented tools and local dependencies first. Obtain the current base SHA and the active .github/a38.json; keep the trusted external policy copy, report, and logs outside the checkout so the PR cannot replace evidence inputs or outputs.
Public adopters run the same complete manifest and report validation using --public, follow the pull request lifecycle for draft timing, and retain their cumulative GitHub CI gates for Ready; the private local-code-gate equivalence below does not apply.
For example, after replacing the paths and SHA with actual values:
agent a38 policy --file /tmp/a38-run/policy.json
agent a38 run --repo . --policy /tmp/a38-run/policy.json \
--base-sha BASE_COMMIT_SHA \
--output /tmp/a38-run/report.md --logs-dir /tmp/a38-run/logs --privateThe runner identifies the GitHub repository from origin by default; for a fork, pass --repository OWNER/NAME with the PR target repository. Without --private or --public, visibility lookup requires --github-session SESSION bound explicitly in $AGENT_HOME/github-accounts.json. The executor verifies the selected account and refuses an absent binding or mismatched login; it never uses ambient host authentication. Explicit --private or --public supplies the report visibility without a GitHub lookup. A38 checks that visibility against the actual repository. The runner records each command's result, exit code, elapsed time and timeout. Jobs whose lock domains do not conflict may run at the same time: non-docker jobs share a CPU domain so lint, type-check and Jest do not overlap each other; docker-heavy may overlap that CPU domain. A lock on the command form coordinates only inside that agent a38 run. Cross-process exclusivity is the adapter lock (executor.config.lock, including the docker-heavy default for compose and http-smoke). A38_MAX_IN_FLIGHT caps concurrency (default 2, integer ≥ 1). After a job failure, independent jobs still run; a dirty tree or HEAD change still aborts the rest. When the change set is markdown-only, every required job is recorded as not_applicable without executing the command. When the change set is guard-docs (markdown files and/or .github/workflows/a38-guard.yml) and not already markdown-only, every required job is recorded as not_applicable with log text omitted: guard-docs change set and without markdown_only: true. Otherwise, configured readme_only.omit_jobs are recorded as not_applicable without executing the command when the change set is README-only; other jobs still run. It exposes A38_HEAD_SHA and A38_BASE_SHA to commands, removes GitHub token variables from job environments, and retains per-job logs locally. It terminates timed-out process groups. An interrupted run or checkout drift must never produce a successful report.
Job adapter behavior is defined separately in A38 job adapters. Use agent a38 job <commands|compose|http-smoke|immutable> --config '<JSON>'; this standard intentionally does not duplicate adapter schemas.
A single job may be re-run directly for diagnosis with agent a38 job, using its exact command from agent a38 run's policy output and setting both required runtime variables A38_HEAD_SHA and A38_BASE_SHA. This is for diagnosis only. Acceptance evidence still requires one complete run on the final clean head, because a report assembled from separate runs is indistinguishable from a complete one.
Inspect the results, fix failures, and repeat the complete run on the final clean head. In the private opt-in path, locally verify the saved report before recording Ready evidence:
agent a38 verify --policy /tmp/a38-run/policy.json \
--file /tmp/a38-run/report.md --repo OWNER/NAME \
--head HEAD_COMMIT_SHA --privateOnly a complete run plus successful local verify for the signed, clean final SHA may be recorded as local_check_pass. That final SHA must be on the open draft with no intervening commit after the verified measurement. Early draft publication may precede this final measurement (pull request lifecycle). Any fix, amend, rebase, or other new SHA requires a new signed clean commit and a complete run and verification from the beginning.
The generated report.md is ready to publish: one short sentence under EN:, one under DE:, and a closed <details> section containing a mandatory table followed by the unchanged machine-readable block in its own nested closed details section. Labels appear on their own lines; blank lines separate the languages and follow </summary> so GitHub renders the enclosed Markdown. The summary describes the evidence without declaring failed or interrupted runs successful. The table lists every recorded A38 job, in policy order (manifest jobs order), with its ID/name, duration, final result and exit code, including failed, errored or timed-out jobs. Display durations in seconds, always rounded up to whole seconds (84.467 → 85 s; 84 → 84 s; 0 → 0 s). Preserve the exact fractional measurements in the original machine block. Rows represent A38 jobs, not every underlying test case in a suite. Job names are escaped so they cannot alter the table or HTML structure. Commands and exact evidence remain in the nested original report. Adopters and plugins use this central output rather than maintaining another format template.
Post the generated report.md unchanged as a PR comment using the PR author's GitHub account, preserving its markers and fenced JSON block. For legacy reports, adding this presentation around the original marked block is allowed only if that block remains byte-for-byte unchanged; preserve all measured data and timestamps, then verify and reassess the edited comment. Publishing is separate from running and verification. Do not paste raw logs containing credentials into the comment and do not hand-edit or reconstruct a passing payload.
Authors who are GitHub Users and currently have write, maintain, or admin on the target repository are not required to post that author report for Ready; the same waiver applies when a User with one of those roles is the latest ready_for_review actor. Independently confirmed markdown-only change sets also waive the author-report gate (reason markdown-only change set) so fork workflow approval and auto-ready can proceed without a local suite. Independently confirmed guard-docs change sets (markdown files and/or .github/workflows/a38-guard.yml, fail-closed) waive the same report gate with reason guard-docs change set and do not fail the .github/workflows/a38-guard.yml bytes-changed policy line; other workflow paths still need maintainer policy approval. The guard waives the report gate, with an explicit status description, except that independently confirmed guard-docs also skips the bytes-changed line for .github/workflows/a38-guard.yml only. Bots and apps cannot grant the write waiver. No-write authors (including forks and triage-only collaborators) must still publish a valid author report before Ready unless the markdown-only or guard-docs waiver applies. Policy, workflow inventory, and migration failures are never waived by write access or by markdown-only or guard-docs detection, except that guard-docs skips the bytes-changed line for .github/workflows/a38-guard.yml only.
The report must contain the exact repository, current head, visibility, full required ID set, job names, commands and policy timeouts. Every result must be pass with exit code zero and finite elapsed time within its timeout, or an authorized not_applicable: either every required job may be not_applicable when the report carries markdown_only: true and exit_code is zero, or a job listed in optional readme_only.omit_jobs when the report carries readme_only: true and exit_code is zero. verify_report stays flag-based: not_applicable is authorized only with markdown_only: true (all jobs) or readme_only: true for configured omit_jobs. Guard-docs is an inventory waiver for the author-report gate (Ready / write-ready reason guard-docs change set); it is not a posted-report path and does not add a report flag. Flagless all-omit reports remain unauthorized. The UTC timestamp must be valid and cannot be materially in the future. Public visibility does not bypass these checks. The guard independently confirms README-only, markdown-only, and guard-docs file inventories and does not trust report omit flags alone.
Only the latest report-like comment by the authenticated PR author is considered. A newer failed or malformed report does not fall back to an older success. Editing or deleting reports, pushing commits and changing the target base require reassessment. Other contributors cannot satisfy the author's reporting requirement by posting a copied block.
On an open draft the bot comment is a friendly greeting that uses a Markdown link whose text is the rules name and whose target is standard_url, not a raw URL in the visible sentence. It does not lecture about draft status, missing reports, write waivers, markdown-only waivers, guard-docs waivers, or tool-attribution, and it does not include the details block (jobs, problems, run command). Independently confirmed markdown-only drafts (every changed path ends with .md, fail-closed, no hard_fail) skip that short greeting and use the Ready markdown-only waiver comment. Independently confirmed guard-docs drafts (markdown files and/or .github/workflows/a38-guard.yml, fail-closed, no hard_fail) skip that short greeting and use the Ready guard-docs waiver comment. Current problems and run instructions appear in the comment once the pull request is Ready (draft=false). observe publishes an explicitly advisory status and is unchanged on drafts. enforce reports success only for valid evidence, with the author-report waivers below (write collaborator, independently confirmed markdown-only, or independently confirmed guard-docs). Independently confirmed markdown-only drafts without hard_fail post enforce success (pass: markdown-only change set; A38 report not required) on A38 / report (<target>). Independently confirmed guard-docs drafts without hard_fail post enforce success (pass: guard-docs change set; A38 report not required) on that context. Other draft pull requests without hard_fail publish no blocking A38 / report (<target>) commit status (not failure, not pending, and not a fabricated pass) and exit 0 so the GitHub Actions check dfx pr guard is not red merely because a draft has no author report yet. Configured not_applicable exclusions may still write success on that context to clear a wrong prior status; that is not a test-pass claim. Separately, the guard scans the live PR title, PR body, and every PR commit message plus author/committer identity (not the diff, not comments). A truncated GitHub pull-commits list (the endpoint caps at 250) is fail-closed. Tool-attribution markers — a generated-with banner, an AI Co-Authored-By trailer, an AI session header, or an AI author identity — fail the assessment with hard_fail. An unscannable or missing commit message is also fail-closed (hard_fail, exit 1 even on draft/observe), same as attribution markers. dfx pr guard then exits 1 even on a draft and even in observe mode. On Ready the comment tells authors to remove attribution markers, supply a non-empty commit message, or both. On hard_fail the guard posts failure on that context against the PR head even on a draft, so the pull request Checks box is red. The draft exemption (omit the blocking status, exit 0) applies only when there is no hard_fail and the draft is not independently confirmed markdown-only or guard-docs. A leftover failure whose description starts with hard_fail: is success-cleared on the next draft reconcile that is no longer hard_fail; that is not a test-pass claim. Other enforce failures on the same head stay.
The author local-CI report is required for Ready when the PR author lacks write, maintain, or admin on the target repository and the change set is not independently markdown-only or guard-docs. If the author currently has one of those roles and is a GitHub User, the report is not required; enforce status succeeds with an explicit waiver description. The same write waiver applies when a GitHub User collaborator with write/maintain/admin is the latest ready_for_review timeline actor or the ready_for_review webhook sender (so a lagged timeline cannot drop the click). Independently confirmed markdown-only change sets waive the report (and the local suite) without granting the write hold: Ready still auto-drafts if required CI is red or pending. Independently confirmed guard-docs change sets waive the report and the local suite the same way (reason guard-docs change set) and skip the .github/workflows/a38-guard.yml bytes-changed policy line; other workflow paths still need maintainer policy approval. Draft does not hide that Ready actor: after an auto-draft, the next reconcile still sees them and lifecycle restores Ready even if CI is red when GitHub mergeable is true and there are no conflicts. Only User actors can grant the write waiver (allowlist); bots and apps cannot, even with write/admin. MEMBER / author_association is not write. A 404 or denied collaborator-permission lookup does not grant it. Timeline pagination 401/403/404 yields no waiver and does not crash assessment. Invalid policy, unclassified workflows, and pr-guard migration failures are not waived by write access or by markdown-only or guard-docs detection, except that guard-docs skips the bytes-changed line for .github/workflows/a38-guard.yml only. No-write authors (forks, triage-only, outside collaborators) who mark Ready without a valid author report still fail unless markdown-only or guard-docs applies; lifecycle still auto-drafts them. That write hold does not apply to confirmed merge conflicts: those always return Ready to Draft. Lifecycle must not convert a Ready PR back to Draft while that write hold applies for missing or red CI.
Once the pull request is Ready (draft=false) without a report waiver: valid evidence → success; missing or invalid evidence → failure. Configure branch protection to require the appropriate enforced status after a successful rollout. A missing or invalid policy is a configuration failure, not permission to pass. A valid A38 report confirms the consistency of an author's declaration; it is not cryptographic proof that commands ran or proof that tests themselves are adequate.
For the private opt-in equivalence, the final Ready join is a separate live read after the same SHA has been pushed. Without a report waiver (write collaborator, independently confirmed markdown-only, or independently confirmed guard-docs), the unchanged generated report must already have been posted; with a waiver, the live join ok/pass after that push is enough for the report gate:
agent pr-guard --repo OWNER/NAME --pr N --dry-run --jsonSet GH_TOKEN or GITHUB_TOKEN in the secure process environment first; never print it or place it in command arguments. Validate the JSON itself: ok is true, status is pass, closed is false, private matches the actual target visibility and is true for this equivalence, and dry_run is true. repo and pr must identify the actual target PR; head and base must equal immediately refreshed current SHAs; policy_revision must equal the active immutable base revision or the exact approved current head revision. Exit zero or state: "success" alone is insufficient, especially in observe mode.
API/configuration errors and missing, malformed, or stale fields block. The latest report-like comment actually authored by the PR author is the only report considered. Re-run the live assessment after a changed head, base, title, body, author comment, or approval.
When the base mode is enforce, the actual stable context A38 / report (<target-branch>) must also be successful on the current head. In observe, A38 / report (observe: <target-branch>) is advisory and is not a Ready gate, but the actual live-validated report remains valid for the private code-gate equivalence. Independently required GitHub-only checks, technical merge restrictions, repository review gates, and human merge remain required in both modes.
An adopting repository does not implement A38. Process, guard, report format, and job adapters live in this repository. The adopter adds only repository configuration that selects jobs and wires the pinned guard.
Copying this document, the guard guide, or any Python from src/agent_cli/ into the adopter is a defect. Adding a YAML interpreter, GitHub Actions step runner, or second local CI engine in the adopter is a defect: required jobs call the same tests CI already runs, through command or executor (A38 job adapters).
Exactly these, plus optional repo-specific contracts listed below:
| Path | Role |
|---|---|
.github/a38.json |
Complete a38/v1 inventory. Every workflow job is either a required jobs[] entry or an exclusions[] entry with a meaningful reason. |
.github/pr-guard.json |
pr-guard/v1 target-branch scope. Enforce the integration branch. If that branch is main (the default), list main in a38.enforce. A non-default main (release PR) stays out of scope. |
.github/workflows/a38-guard.yml |
Copy the example workflow. Replace USES_REF_PIN_ME with a reviewed 40-hex commit SHA of this repository. Do not check out the pull request. |
| Contributing guide | A short pointer to this document (and the guard guide) at a pinned SHA. Not a copy of either document. |
Optional, when the adopter already contracts GitHub workflows in-repo (static runner/policy tests): extend those tests so the guard workflow stays pinned to a 40-hex SHA, does not check out the PR, has no run step, and uses only the documented events and permissions. That is repository policy, not A38 logic.
Ignore generated coverage files (for example .coverage) when a required job would otherwise leave untracked files; A38 refuses a dirty tree.
Do these steps in order. Do not skip inventory.
-
List every job in
.github/workflows/*.{yml,yaml}, including jobs in reusable workflows. A job that CI never runs on pull requests still needs an exclusion if the workflow file exists. -
Classify each pair
(workflow, job)exactly once: required local measurement →jobs[]; deploy, publish, housekeeping, GitHub-only, or the guard itself →exclusions[]with a reason long enough to explain why it is not an author test (not a one-word label). -
For each required job write either
command(one exact local shell command) orexecutorwithadaptercommands,compose,http-smoke, orimmutable. The steps must be the tests that CI job runs (same binaries, same flags that change results). Do not pointstepsat a helper that re-parses the workflow YAML. -
Set
timeout_sto a finite budget for that job. Expected wall-clock times and GitHubtimeout-minutesbelong in the adopting repo's own CI docs, not in this standard. -
Copy
examples/a38-guard.ymlonto the default branch path.github/workflows/a38-guard.yml. PinDFXswiss/agent/.github/actions/a38-guard@<40-hex>. Keepruns-onon the same trusted runner the repo already uses forpull_request_targetbots when it has one; otherwise the example'subuntu-latestis the starting point. Do not addactions/checkout. Do not add arunstep. -
Add
.github/pr-guard.jsonfrom the example with this repository's actual integration-branch names. If the default branch ismain, listmainina38.enforce. Do not expect A38 on a non-defaultmain(release PR). -
Add the contributing pointer. Name
.github/a38.jsonand.github/pr-guard.json. Link here and to dfx pr guard. -
Validate the manifest with the Agent at the same pin as the guard action:
agent a38 policy --file .github/a38.json
The inventory must be complete: every head workflow job classified, no extra pairs, no duplicate keys.
-
Merge the adoption PR onto the default branch. The guard is inactive until that workflow file exists on the trusted default branch. The adoption PR itself has no base manifest; authors with write are not required to post an A38 report for Ready. After merge, every later workflow or policy-file change is an A38 policy migration (
A38-POLICY-APPROVAL:v1on the exact head and base).
docs/a38.md, a second report format, or a seconddfx pr guardimplementation.- A script that loads
.github/workflows/*.ymland executesrun:steps locally so A38 “measures CI exactly”. Use job adapters and the CI job's own commands. - Optional guard features (
workflow_approval,lifecycle/auto_ready,environment_approval) unless an owner decides to turn those objects on. Forkworkflow_approvalbelongs only on repositories that actually hold forkpull_requestruns.environment_approvalbelongs only on repositories that wait on a named GitHub environment. Missing features stay missing; do not copy another adopter'spermissions:block blindly. - Internal hostnames or private repo names in a public adopter's docs or comments.
runs-onlabels that GitHub needs are allowed.
Worked public example of jobs[] / exclusions[] shape: examples/a38.json. Adapters commands, compose, http-smoke, and immutable cover real CI jobs; do not add a workflow interpreter in the adopter.
Follow dfx pr guard installation and operation for the pinned trusted action and standalone reconciliation commands. The guard treats PR contents as data and never executes PR code. A GitHub Actions installation needs working Actions capacity; a trusted external host can reconcile through the same CLI when Actions are unavailable. Put the event workflow on the default branch, verify real opened/comment-edited/comment-deleted/push scenarios, then enable the required enforced status. Human review and merge rules remain in effect.
Once run arguments identify a safe output outside the repository, the runner removes any previous report before policy loading or other run preflight. Parser usage errors and unsafe or ambiguous destinations leave existing files untouched; those failures do not authorize deleting a file.