diff --git a/docs/development/ENGINEERING_PLATFORM_ROADMAP.md b/docs/development/ENGINEERING_PLATFORM_ROADMAP.md index 3f7ac6fc..e3ae10c3 100644 --- a/docs/development/ENGINEERING_PLATFORM_ROADMAP.md +++ b/docs/development/ENGINEERING_PLATFORM_ROADMAP.md @@ -4,9 +4,12 @@ Repository evidence recorded on 2026-09-09 reads `origin/main` as `0a98d0ca2395bd3b6b50deffd3139206b75c16e5` at this roadmap update. PyPI -holds immutable 2.3.1 wheel and sdist bytes; protected-main reconciliation -moved the canonical source projections forward to 2.3.2 without rewriting that -published identity. The observed Mac has one 2.3.1 server process/data root and +holds immutable 2.3.1 wheel and sdist bytes; the historical protected-main +reconciliation receipt records the earlier 2.3.2 source preparation without +rewriting that published identity. The current bounded engineering candidate +uses the EP product version helper to project 2.3.3; it is source-prepared +only and has no publication or release-closure claim. The observed Mac has one +2.3.1 server process/data root and a distinct 2.3.0 PlatformIO PATH candidate. This is explicit-path investigation evidence, not proof of Mac-wide uniqueness and not authorization to remove or cut over either installation. CENTRAL schema 56, engineering-storage schema 41 @@ -15,7 +18,7 @@ and repository-attachment schema 1.0 remain separate contracts. | Increment | Owning repository | Bounded result | Dependencies / acceptance | | --- | --- | --- | --- | | RL-1 | engineering-platform | Durable EP release-operation record, product-wide exclusive operation lock, exact wheel/sdist identities and separate `PUBLISHED`/`RELEASE_COMPLETE` states | SOURCE_FIXED through #153 `6919898`; the current parity increment also hardens owner-locked transitions, immutable policy identity, JSON recovery validation and `CLEANUP_PENDING` resume. No new release operation has been executed. | -| RL-2 | engineering-platform | Protected-main release workflow, exact-main qualification, registry readback, immutable `PUBLISHED` evidence, separate `RELEASE_COMPLETE` closure, receipt and scoped cleanup | The current parity increment retains `QUALIFIED` in a draft GitHub Release before PyPI, rejects unproven existing publications, reads back both exact distributions and completes cleanup before terminalization. 2.3.2 remains source-prepared only; no PyPI publication or release closure is claimed. | +| RL-2 | engineering-platform | Protected-main release workflow, exact-main qualification, registry readback, immutable `PUBLISHED` evidence, separate `RELEASE_COMPLETE` closure, receipt and scoped cleanup | The current parity increment retains `QUALIFIED` in a draft GitHub Release before PyPI, rejects unproven existing publications, reads back both exact distributions and completes cleanup before terminalization. The historical 2.3.2 receipt remains evidence for that earlier reconciliation; the current 2.3.3 engineering candidate is source-prepared only. No PyPI publication or release closure is claimed. | | RL-3 | engineering-platform | Durable first-failure `CLEANUP_PENDING` evidence and controlled retry of release cleanup | SOURCE_FIXED: #165 `0a98d0c`. It canonicalizes and atomically hydrates a matching remote PENDING receipt, retains it on repeated cleanup failure, and detects dangling symlinks or post-delete residuals. No release operation has been dispatched. | | OI-1 | engineering-platform | Read-only operational-installation resolver/diagnostic | SOURCE_FIXED: #111, #114 `9ef29bb`, #116 `71779d5`, #118 `f29006c`, #131 `28293b0`, #132 `d7efd67`, #133 `98e70e9`, #135 `99cbd4f`, #157 `f28fc84`, #161 `3161a4e`. The selected venv launcher and the actual server response remain separate from PATH/source observations. Explicit-path inventory only; no installation is verified. | | OI-2 | engineering-platform | One EP-owned install/update/repair record and crash-resumable lifecycle | PARTIAL_SOURCE_FIXED: #113 `38b222a`, #121 `1bfe729`, #122 `a6f6c10`, #123 `2c4b081`, #127 `b77a638`, #128 `73c9729`, #129 `aadb3a5`, #130 `5073ee1`, #137 `a517ce3`, #144 `2848c52`, #158 `62eb6c4`. The executor serializes, journals and resumes explicit inventory/quiesce/backup/migrate/activate/verify actions; it atomically replaces only the exact registered record and performs operation-scoped cleanup. A product-specific runtime/service/migration adapter, a real update, and operational cleanup have not run. | @@ -65,6 +68,35 @@ receipts remain product-specific. A production publication or an actual Mac installation/update remains outside this source order until separately authorized. +## Managed post-assurance publication closure — paused candidate qualification + +`EP_MANAGED_POST_ASSURANCE_PUBLICATION_CLOSURE_V1` remains a bounded EP-owned +repair, separate from the release and operational-installation lanes above. +Its historical candidate is `7c347f887ddd01b15f70234a324c2de2fd9844a2`; the +source lineage retains its patch-equivalent contract update and the local +repair branch `codex/ep-managed-post-assurance-publication-closure-v1` had +last committed candidate `56c3b912cda7d0a9fc911c5b3a298083e44b3fa9` at this +pause checkpoint. The branch must be made stable and its exact final SHA must +be explicitly re-authorized before any execution resumes. + +The source candidate contains the typed Managed-adoption route, current +validation binding, separate Quality/Security assurance, and fail-closed +publication readback handling. That is source evidence only: no isolated +candidate-wheel qualification run, durable owner-authority record, current +validation PASS, independent Quality/Security PASS, remote repair branch, or +draft implementation PR is claimed here. The observed primary EP runtime is +2.3.1 and is not the execution route for this repair; it must not be changed +by this work. + +The next permitted continuation is one separately authorized, isolated +candidate-wheel qualification after the final SHA is frozen. It must use the +product-owned DEVELOPMENT route and a separate data root, execute one Managed +adopted-candidate run with zero corrective rounds, and stop immediately after +one GitHub-readback-verified draft PR. It must not merge, release, install, +change the primary runtime/CENTRAL, or activate a later roadmap increment. +This pause record is documentary context, not lifecycle authority or +qualification evidence. + ## Subagent orchestration and efficiency — retained audit and planned lane `EP_SUBAGENT_ORCHESTRATION_AND_EFFICIENCY_V1` records the diff --git a/docs/development/SUBAGENT_ORCHESTRATION_V1_DAG.json b/docs/development/SUBAGENT_ORCHESTRATION_V1_DAG.json index 33876e89..8d35b0fa 100644 --- a/docs/development/SUBAGENT_ORCHESTRATION_V1_DAG.json +++ b/docs/development/SUBAGENT_ORCHESTRATION_V1_DAG.json @@ -22,7 +22,18 @@ "depends_on": [], "owning_work": "EP_MANAGED_POST_ASSURANCE_PUBLICATION_CLOSURE_V1", "requirement": "Read back qualification of the existing post-assurance publication contract for the exact source/artifact before SA-PUB activation; this documentary graph does not implement or close that work.", - "evidence": [] + "evidence": [ + { + "kind": "SOURCE_PAUSE_CHECKPOINT", + "observed_at": "2026-09-10", + "status": "NOT_QUALIFIED", + "historical_candidate": "7c347f887ddd01b15f70234a324c2de2fd9844a2", + "branch": "codex/ep-managed-post-assurance-publication-closure-v1", + "last_committed_candidate": "56c3b912cda7d0a9fc911c5b3a298083e44b3fa9", + "summary": "The local source continuation is paused pending a stable, explicitly re-authorized final SHA and one isolated candidate-wheel Managed qualification. No owner-authority record, current validation/Quality/Security PASS, remote repair branch, draft PR, source delivery, or installed-runtime qualification is evidenced by this record.", + "continuation_boundary": "Use only the separately authorized isolated DEVELOPMENT route; preserve the primary runtime/CENTRAL and stop after one GitHub-readback-verified draft PR." + } + ] } ], "nodes": [ diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index dbdbe313..b275160a 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -40,6 +40,41 @@ hosted checks and finalization; a new SHA, phase, resume or PR does not reset it. The Console projects the same stored review identities and repair rounds for live and historical runs. +For a new Managed implementation, the first draft implementation PR is a +separate, post-assurance dispatch: bounded implementation -> local validation +-> independent Quality and Security reviews for the same candidate/profile -> +host publication gate -> draft PR. `EXECUTE_AGENT` is used for both the +implementation and that publication dispatch, so prompt authority is selected +from trusted transaction kind, phase, next action, checkpointed PR lineage and +current assurance evidence together. An action string or objective text alone +never grants PR creation. Pre-publication repair normally has no PR and returns +to validation and both reviews; repair of a known PR preserves its exact +lineage. A recovered implementation result is not a publication result. + +The local-validation decision is not inferred from provider prose. The host +loads the persisted validation profile and requires a successful terminal +command receipt for every required control at the current repair ordinal. The +selected tier, version, registry reference, required-control set and launcher +bindings form a digest that is embedded in the assurance profile. A missing +receipt, non-zero exit, unavailable/skipped result, older repair ordinal or +different profile therefore cannot authorize publication. + +The host rejects an early provider-reported PR with +`implementation_pr_before_assurance`, and verifies candidate/branch identity +before and after publication. This is host sequencing and result detection, not +a universal technical proof that an arbitrary provider could not use every +possible external HTTP or absolute-path route to create a remote PR. The local +validation provider is read-only sandboxed; existing product boundaries do not +claim a general remote-write broker or network proxy for all implementation +provider routes. + +Before a first-publication provider turn, including after restart, the host +reads GitHub for the checkpointed branch. It accepts only one open draft whose +base and exact head SHA match the reviewed candidate. A mismatching result +blocks; an exact match is reconciled without replaying PR creation. An +interrupted publication turn likewise cannot enter the generic provider retry +loop before that exact readback. + The immutable profile lists repository, remote, upstream, clean-worktree, branch, workspace authorization, host and capability qualification, providers, datastore, active-lease and Producer Contract requirements. Facts are observed diff --git a/docs/engineering/EXECUTION_HOST_CONTRACT.md b/docs/engineering/EXECUTION_HOST_CONTRACT.md index 208e3761..982689c1 100644 --- a/docs/engineering/EXECUTION_HOST_CONTRACT.md +++ b/docs/engineering/EXECUTION_HOST_CONTRACT.md @@ -77,10 +77,12 @@ The Execution Host acquires the exclusive run lease, performs admission and synchronizes the repository before it invokes the implementation agent. A managed, owner-authorized transaction then runs in the Codex CLI's `danger-full-access` sandbox profile so that the already-authorized bounded -transaction can create its branch, stage its own scoped changes, commit and -open its draft pull request. This is not an unrestricted lifecycle authority: -the supplied transaction scope, branch and pull-request rules still apply. The -runner may mark a pull request ready for review, but its merge remains +transaction can create its branch, stage its own scoped changes and commit. +The first draft pull request is a separate host-owned publication dispatch, +permitted only after current read-only local validation and independent Quality +and Security assurance for that exact candidate. This is not an unrestricted +lifecycle authority: the supplied transaction scope, branch and pull-request +rules still apply. The runner may mark a pull request ready for review, but its merge remains operator-owned. A green, open pull request is persisted as `WAIT_FOR_OPERATOR_MERGE`; it is not a failed execution and it must keep its Inbox position until the operator merges it or explicitly aborts the hand-off. diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index 918d6040..17a6b9d8 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -223,16 +223,35 @@ documentation tier. For a Managed implementation, the Execution Host first creates and pushes the bounded branch without creating a pull request. The visible **Local repository validation** step discovers and runs the target repository's canonical required -local validation. It may make scoped production-code and test corrections on -that same branch and retries at most three times. Each attempt records its safe -problem, corrective action, result and commit evidence. Only a passing attempt -may create the draft implementation pull request. Remote GitHub check repair -remains a separate, later bounded gate. +local validation as a read-only measurement. It cannot modify files, index, +commits, branches, remotes, pull requests, or other remote state; it neither +requires nor creates a PR. A failing measurement does not authorize a local +repair itself. The host may spend the one existing run-wide repair budget on a +bounded repair of the same candidate, then returns that candidate through +local validation and both independent Quality and Security reviews. Only after +current validation and both reviews bind to the same clean candidate/profile +does the separate host-owned first-publication gate create the draft +implementation PR. Remote GitHub check repair remains a separate, later +bounded gate and preserves an already-known PR lineage. + +For publication eligibility, a textual validation summary is reporting only. +The canonical decision requires an exit-code-zero terminal receipt for every +control in the persisted profile at the current run-wide repair ordinal. The +assurance profile binds the digest of that selected profile and its immutable +control launchers. Missing, failed, unavailable, skipped, stale or differently +profiled evidence blocks before publication and cannot be replaced by passing +Quality/Security records. + +If the host restarts at the first-publication checkpoint, it does not +synchronize back to `main` or rerun implementation. It verifies the clean +checkpointed branch and candidate, then performs exact GitHub readback before +any create attempt. One matching open draft is resumed; a mismatch blocks and +does not trigger a second PR. Both bounded gates preserve the same immutable per-attempt shape: iteration, observation time, observed problem, proposed action, safe agent summary, commit evidence and outcome. Local validation uses `validated`, -`validation_failed` or `agent_failed`; PR repair uses +`validation_failed` or `agent_failed`; repair uses `submitted_for_recheck`, `agent_failed` or `agent_timed_out`. The latter is a host-owned deadline outcome, not an invitation to start another repair: the run is blocked with its evidence intact and requires a new explicit recovery diff --git a/package-lock.json b/package-lock.json index b93b112a..fd6fe892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engineering-platform-browser-validation", - "version": "2.3.2", + "version": "2.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "engineering-platform-browser-validation", - "version": "2.3.2", + "version": "2.3.3", "devDependencies": { "@playwright/test": "1.62.1" } diff --git a/package.json b/package.json index 89b2b234..82e730e4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "engineering-platform-browser-validation", "private": true, - "version": "2.3.2", + "version": "2.3.3", "scripts": { "test:engineering-dashboard": "PYTHONPATH=src python3 -m engineering_platform.dashboard_browser_validation", "test:engineering-dashboard-logic": "node --test tests/engineering/dashboard_status_store.test.mjs tests/engineering/ui_localization_contract.test.mjs", diff --git a/pyproject.toml b/pyproject.toml index dfb3d570..2ccc4d57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "engineering-platform" -version = "2.3.2" +version = "2.3.3" description = "Local-first Engineering Platform execution operations runtime" readme = "README.md" requires-python = ">=3.11" diff --git a/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json b/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json index 8d0541c9..ce5efe9d 100644 --- a/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json +++ b/src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json @@ -3,7 +3,7 @@ "platform": { "id": "engineering-platform", "name": "Engineering Platform", - "version": "2.3.2", + "version": "2.3.3", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 diff --git a/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json b/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json index fe639b4d..458ac51d 100644 --- a/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json +++ b/src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json @@ -1,15 +1,15 @@ { "bootstrap_contract": "2026.12", "checkpoint_format": 1, - "dashboard_version": "2.3.2", + "dashboard_version": "2.3.3", "handoff_protocol": 1, "memory_format": 2, "minimum_codex_cli": "0.146.0", "inbox_protocol": 1, - "platform_version": "2.3.2", + "platform_version": "2.3.3", "report_format": 2, - "runner_version": "2.3.2", + "runner_version": "2.3.3", "status_model": 1, "storage_schema": 41, - "watcher_version": "2.3.2" + "watcher_version": "2.3.3" } diff --git a/src/engineering_platform/ENGINEERING_QUALIFICATION.md b/src/engineering_platform/ENGINEERING_QUALIFICATION.md index 9311b5cd..b670984e 100644 --- a/src/engineering_platform/ENGINEERING_QUALIFICATION.md +++ b/src/engineering_platform/ENGINEERING_QUALIFICATION.md @@ -8,7 +8,7 @@ registry and writes local, git-ignored reports under `.engineering/qualification | --- | --- | --- | --- | --- | | Repository Initialization | Clean and dirty checkout | Reconcile or `BLOCKED` with diagnostics | local qualification report | Registered | | Checkpoint Resume | Interrupted transaction | Resume without duplicate PR | local qualification report | Registered | -| Implementation Lifecycle, Validation Loop, Repair Loop | bounded PR and failing validation | repair remains bounded and lifecycle continues | local qualification report | Registered | +| Implementation Lifecycle, Validation Loop, Repair Loop | adopted or newly implemented candidate, terminal validation controls, interrupted first publication and failing validation | every required current control and both independent reviews precede one exact draft PR; restart reconciles by branch/base/SHA without duplicate creation; repair remains bounded | local qualification report | Registered | | Owner Authorization, Ready For Review, Automatic Merge | authorized green PR | only runner-controlled progression | local qualification report | Registered | | Repository Reconciliation, Finalization, Repository Cleanup | merged and squash-merged transaction | evidence-driven reconciliation and `WORKSPACE_READY` | local qualification report | Registered | | Engineering Memory, Progress Reporting, Engineering Reports | repeated transaction | bounded advisory memory and explainable output | local qualification report | Registered | diff --git a/src/engineering_platform/agent_state.py b/src/engineering_platform/agent_state.py index f0c46e72..3f9106f6 100644 --- a/src/engineering_platform/agent_state.py +++ b/src/engineering_platform/agent_state.py @@ -379,12 +379,21 @@ def from_dict(cls, raw: object) -> "TransactionState": raise StateError("checkpoint quality evidence is invalid or unsafe") profile_fields = {"version", "digest", "candidate_sha"} current_profile_fields = profile_fields | {"criteria_digest"} + candidate_bound_profile_fields = current_profile_fields | {"validation_profile_digest"} if state.assurance_profile is not None and ( not isinstance(state.assurance_profile, dict) - or set(state.assurance_profile) not in (profile_fields, current_profile_fields) + or set(state.assurance_profile) not in ( + profile_fields, current_profile_fields, candidate_bound_profile_fields, + ) or not all(isinstance(value, str) and value for value in state.assurance_profile.values()) or not re.fullmatch(r"sha256:[0-9a-f]{64}", state.assurance_profile["digest"]) or not re.fullmatch(r"[0-9a-f]{40}", state.assurance_profile["candidate_sha"]) + or ( + "validation_profile_digest" in state.assurance_profile + and not re.fullmatch( + r"sha256:[0-9a-f]{64}", state.assurance_profile["validation_profile_digest"], + ) + ) ): raise StateError("checkpoint assurance profile is invalid") review_fields = {"reviewer", "status", "candidate_sha", "profile_digest", "invocation_id", "findings"} diff --git a/src/engineering_platform/execution_executor.py b/src/engineering_platform/execution_executor.py index 8b1732fb..3da91f60 100644 --- a/src/engineering_platform/execution_executor.py +++ b/src/engineering_platform/execution_executor.py @@ -291,10 +291,11 @@ def load_validation_failure_diagnostic( _format_cli_failure = format_cli_failure # A managed Engineering transaction has already passed host-owned admission, -# repository synchronization and an exclusive execution lease. It must be -# able to create its bounded branch, commit, and draft PR; `workspace-write` +# repository synchronization and an exclusive execution lease. It must be able +# to create its bounded branch and commit; the later, separately host-gated +# publication dispatch may create the first draft PR. `workspace-write` # deliberately rejects Git index writes and therefore cannot complete that -# contract. Review-only invocations remain read-only below. +# contract. Review-only invocations remain read-only below. MANAGED_EXECUTION_SANDBOX = "danger-full-access" class CodexCliClient: diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index de684999..bc2ddbd2 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -202,6 +202,198 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: ... _format_cli_failure = executor_format_cli_failure +def _has_current_assurance_evidence(state: TransactionState) -> bool: + """Return whether trusted state binds both mandatory reviews to one candidate. + + This deliberately derives authority from checkpointed host evidence, not + from a provider-supplied action name, objective text, or PR number. + """ + profile = state.assurance_profile + if not isinstance(profile, dict): + return False + candidate, digest = profile.get("candidate_sha"), profile.get("digest") + if not isinstance(candidate, str) or not isinstance(digest, str): + return False + current = [ + review for review in state.assurance_reviews + if review.get("candidate_sha") == candidate and review.get("profile_digest") == digest + ] + return all( + any(review.get("reviewer") == role and review.get("status") == "PASS" for review in current) + for role in ("quality", "security") + ) and not any( + finding.get("blocking") and finding.get("disposition") == "OPEN" + for review in current for finding in review.get("findings", []) + if isinstance(finding, dict) + ) + + +def _validation_profile_digest(validation_context: dict[str, object] | None) -> str | None: + """Return the immutable identity of the selected validation profile.""" + if not isinstance(validation_context, dict): + return None + identity = { + "selected_validation_tier": validation_context.get("selected_validation_tier"), + "validation_profile_version": validation_context.get("validation_profile_version"), + "profile_reference": validation_context.get("profile_reference"), + "profile_selection_source": validation_context.get("profile_selection_source"), + "required_validation_controls": validation_context.get("required_validation_controls"), + "control_bindings": validation_context.get("control_bindings"), + } + if ( + not isinstance(identity["selected_validation_tier"], str) + or not isinstance(identity["validation_profile_version"], str) + or not isinstance(identity["profile_reference"], str) + or not isinstance(identity["profile_selection_source"], str) + or not isinstance(identity["required_validation_controls"], tuple) + or not identity["required_validation_controls"] + or not isinstance(identity["control_bindings"], tuple) + ): + return None + return "sha256:" + hashlib.sha256( + json.dumps(identity, sort_keys=True, separators=(",", ":")).encode("utf-8") + ).hexdigest() + + +def _has_current_local_validation_evidence( + state: TransactionState, validation_context: dict[str, object] | None, +) -> bool: + """Require terminal PASS receipts for every current, required control.""" + profile = state.assurance_profile + if not isinstance(profile, dict): + return False + candidate = profile.get("candidate_sha") + if not isinstance(candidate, str) or state.last_verified_sha != candidate: + return False + if not state.local_validation_audit or state.local_validation_audit[-1].get("outcome") != "validated": + return False + profile_digest = _validation_profile_digest(validation_context) + if profile_digest is None or profile.get("validation_profile_digest") != profile_digest: + return False + return _required_validation_controls_pass(state, validation_context) + + +def _required_validation_controls_pass( + state: TransactionState, validation_context: dict[str, object] | None, +) -> bool: + """Require current successful command terminals for the persisted profile.""" + required = validation_context.get("required_validation_controls") if validation_context else None + controls = validation_context.get("controls") if validation_context else None + if not isinstance(required, tuple) or not required or not isinstance(controls, dict): + return False + for validation_id in required: + control = controls.get(validation_id) + if not isinstance(control, dict) or ( + control.get("required_for_profile") is not True + or control.get("execution_status") != "EXECUTED" + or control.get("result") != "PASS" + or control.get("currentness") != state.repair_iterations + or control.get("exit_code") != 0 + or not isinstance(control.get("started_at"), str) + or not isinstance(control.get("ended_at"), str) + ): + return False + return True + + +def _managed_prompt_phase( + state: TransactionState | None, + validation_context: dict[str, object] | None = None, +) -> str: + """Select one mutually exclusive provider hand-off from trusted state. + + ``EXECUTE_AGENT`` is shared by implementation and the later publication + dispatch. The latter is privileged only when its complete host evidence + is present; an action string alone is intentionally fail-closed. + """ + if state is None: + return "UNSPECIFIED" + if state.execution_mode == "GENESIS": + return "GENESIS" + if state.transaction_kind == "RECONCILIATION": + return "RECONCILIATION" + if state.action_intent == "VALIDATION_ONLY": + return "VALIDATION_ONLY" + if state.transaction_kind == "FINALIZATION": + return "FINALIZATION" + if state.phase == "LOCAL_REPOSITORY_VALIDATION": + return "LOCAL_VALIDATION" + if state.phase == "QUALITY_CONTROL_AGENT": + return "ASSURANCE_REVIEW" + if state.phase == "REPAIR_AGENT": + return "EXISTING_PR_REPAIR" if state.pull_request else "PREPUBLICATION_REPAIR" + if ( + state.execution_mode == "MANAGED" + and state.transaction_kind == "IMPLEMENTATION" + and state.phase == "EXECUTE_AGENT" + and state.next_action == "publish_first_implementation_pull_request" + and state.owner_authorized + and state.pull_request is None + and _has_current_local_validation_evidence(state, validation_context) + and _has_current_assurance_evidence(state) + ): + return "FIRST_PUBLICATION" + if state.execution_mode == "MANAGED" and state.transaction_kind == "IMPLEMENTATION": + return "INITIAL_IMPLEMENTATION" + return "UNSPECIFIED" + + +def _managed_phase_instructions( + state: TransactionState | None, + validation_context: dict[str, object] | None = None, +) -> str: + """Return the single phase-consistent mutation and result contract.""" + phase = _managed_prompt_phase(state, validation_context) + contracts = { + "INITIAL_IMPLEMENTATION": """ +Implementation hand-off boundary (host-owned and non-negotiable): +- Edit only the admitted checkout, branch and objective scope. The existing execution contract may create, commit and push that bounded branch. +- Do not create a draft or normal implementation pull request and do not mark a pull request ready for review. +- Return the bounded branch, actual commit/evidence, and `pull_request: null`. The host next performs local validation and independent quality and security reviews; only its later post-assurance publication gate may create the first PR. +""", + "LOCAL_VALIDATION": """ +Local repository validation hand-off boundary (read-only measurement): +- Inspect only the selected candidate. Do not modify code, index, commits, branches, remotes, pull requests, or other remote state. +- A pull request is neither required nor permitted for successful validation. Return only applicable validation evidence and result; the host decides any repair or publication. +""", + "ASSURANCE_REVIEW": """ +Mandatory assurance hand-off boundary (read-only): +- Review the host-pinned candidate and applicable profile using the real reviewer result contract. Do not edit, commit, push, create a pull request, or run a mutating quality-control hand-off. +- Do not reuse a conclusion from another candidate, profile, run, or reviewer invocation. +""", + "PREPUBLICATION_REPAIR": """ +Pre-publication repair hand-off boundary: +- Repair the same bounded candidate and branch within the host-owned shared repair budget. No pull request exists yet; do not require, create, or invent one. +- Return the branch and current candidate evidence with `pull_request: null`. The host will revalidate and re-review before considering publication. +""", + "EXISTING_PR_REPAIR": """ +Existing implementation-PR repair hand-off boundary: +- Preserve exactly the checkpointed branch and pull-request identity. Do not create a replacement or second pull request. +- A changed candidate must return to host-owned validation and both independent reviews before renewed delivery eligibility. +""", + "FIRST_PUBLICATION": """ +First implementation publication hand-off boundary: +- The host has already established current validation plus independent quality and security assurance for this exact candidate. +- Do not edit files, index, commits, branch, tests, configuration, or evidence. Create exactly one draft implementation pull request for the existing branch and unchanged current SHA, then return that branch, PR identity, and SHA. +- Do not merge, release, deploy, tag, change repository settings, or start a new QA or repair loop. +""", + "FINALIZATION": """ +PR hand-off boundary (host-owned and non-negotiable): +- Create or repair only the bounded finalization pull request, preserving any checkpointed pull-request number and branch. Never create a replacement pull request. +- Return the required JSON object immediately after that hand-off. Do not + poll or wait for GitHub checks, review, merge, reconciliation, or other external terminal evidence. +""", + "VALIDATION_ONLY": """ +Validation-only boundary: +- Perform only the requested read-only validation. Do not invent or create a branch, commit, pull request, or remote mutation. +""", + } + return contracts.get(phase, """ +Fail-closed Managed hand-off boundary: +- State does not establish authority for a pull-request operation. Do not create, replace, ready, merge, or otherwise mutate a pull request. +""") + + def assemble_prompt( prompt_path: Path, state: TransactionState | None, @@ -209,6 +401,7 @@ def assemble_prompt( managed_target: Path | None = None, reviewer_evidence: ReviewerEvidence | None = None, role: ProviderRole | None = None, + validation_context: dict[str, object] | None = None, ) -> str: objective = prompt_path.read_text(encoding="utf-8") provider_role = role or role_for_phase(state.phase if state else "EXECUTE_AGENT") @@ -231,8 +424,7 @@ def assemble_prompt( `COMPLETE`, `repository_reconciled`, and the pushed main commit SHA only after verifying a clean workspace and that `main` contains that exact commit.""" if state and state.transaction_kind == "RECONCILIATION" - else - """The runner holds explicit owner authorization for this exact bounded transaction. You may create, commit and push one bounded branch and draft pull request, or repair that same pull request. The runner may mark that pull request ready for review, but only the human operator may merge it. Do not merge, release, deploy, tag, publish, upload, change repository settings, bypass protection, or expand the objective.""" + else """The runner's owner authorization is bounded by the current host phase; it does not by itself authorize every pull-request operation. Do not merge, release, deploy, tag, publish, upload, change repository settings, bypass protection, or expand the objective.""" if state and state.owner_authorized else "Do not create a merge, release, deployment, daemon, remote-control, or architecture authority beyond the supplied objective." ) @@ -299,30 +491,14 @@ def assemble_prompt( Ledger bootstrap: """ + json.dumps(investigation_ledger.to_prompt_dict(), sort_keys=True) + "\n" - pr_handoff = "" if not state or state.execution_mode == "GENESIS" else """ -PR hand-off boundary (host-owned and non-negotiable): -- Your work ends when the bounded branch and its pull request have been - created or repaired, pushed, and locally validated. -- If this is a repair, preserve the exact checkpointed pull-request number - and branch. Never create a replacement pull request. -- Return the required JSON object immediately after that hand-off. Do not - poll or wait for GitHub checks, review, merge, Finalization, reconciliation, - or any other external terminal evidence. The Execution Host alone records - the pull request, polls checks, and schedules at most three bounded repairs. -- Write pull-request Markdown with real line breaks. Never serialize a line - break as the literal characters `\\n`. -""" - local_gate = "" if not state or not ( - state.execution_mode == "MANAGED" and state.transaction_kind == "IMPLEMENTATION" and state.phase == "EXECUTE_AGENT" - ) else """ -Local validation hand-off boundary: -- Create, commit and push the bounded implementation branch, but do not create a pull request yet. -- Return that branch with `pull_request: null` after relevant focused validation. The host owns the next local repository validation gate and only that gate may create the implementation PR after the canonical suite passes. -""" + phase_handoff = ( + "" if not state or state.execution_mode == "GENESIS" + else _managed_phase_instructions(state, validation_context) + ) return f"""You are executing one bounded Engineering Platform transaction. Provider role: {provider_role.value}. Context projection: {projection.budget_version}; source items: {projection.source_item_count}; omitted lower-priority items: {projection.omitted_low_priority_count}.{context_scope_instruction} Read BOOTSTRAP.md, ENGINEERING_METHOD.md, PROMPT_INITIALIZATION.md and AGENTS.md from the actual repository before acting. Repository and GitHub evidence override this checkpoint: {resume} -{authority}{genesis}{managed_synchronization}{managed_admission}{shared_evidence}{invocation_read_reuse}{primary_tool_loop}{local_gate}{pr_handoff} +{authority}{genesis}{managed_synchronization}{managed_admission}{shared_evidence}{invocation_read_reuse}{primary_tool_loop}{phase_handoff} Supplied bounded objective follows:\n\n{projection.text}\n{managed_boundary}\n\nReturn only one JSON object with terminal_state (COMPLETE, WAITING, BLOCKED, or FAILED), branch, pull_request, terminal_condition (repository_reconciled, open_pr_checks_terminal, external_blocked, or local_commit_reconciled), diagnostic, repository_path, commit_sha, validation_evidence, quality_evidence and validation_disposition. validation_evidence is a bounded list of executed validation {{command, result}} summaries; use [] when none ran. quality_evidence is [] except for the autonomous quality-control stage, where it contains only bounded, executed {{activity, result}} records. validation_disposition is product_failure unless the required suite failed for an environmental instability that you demonstrated with bounded evidence, such as an isolated rerun of the same failing check passing without a code change. It never makes a failed suite pass. Never include secrets, tokens, headers, environment values, prompts, repository file contents, stack traces, or raw command output. Use null for other fields that do not apply. The diagnostic must be a short human-readable reason without secrets, tokens, headers, environment values, prompt content, repository file content, stack traces, or raw command output.""" @@ -739,13 +915,7 @@ def _record_validation_evidence(self, state: TransactionState, result: AgentResu required=True, currentness=state.repair_iterations, central_database=self.store.central_database, ) - validation_id = ( - "git_diff_check" if kind == "format_or_diff" else - "documentation_contract" if kind == "documentation_contract" else - self._validation_id(command, kind) if kind == "browser_e2e" else - "repository_suite" if kind == "tests" and tier == "FULL" else - "engineering_python" if kind == "tests" else f"validation_{kind}" - ) + validation_id = self._validation_id_for_profile(command, kind, tier) record_validation_control_result( self.root, run_id=state.run_id, validation_id=validation_id, category="agent", control_identity=command[:160], required_for_profile=validation_id in required_controls, execution_status="EXECUTED", @@ -1037,6 +1207,11 @@ def _advance_after_repair_agent_result(self, repair: TransactionState, result: A return self._save_terminal(repair, result.terminal_state, "external_action_required", result.diagnostic) if result.pull_request not in {None, repair.pull_request}: return self._save_terminal(repair, "BLOCKED", "bounded_scope_conflict", "Repair did not preserve the bounded pull request.") + # A repair result may not silently move its work to another branch and + # then have the host overwrite that identity below. The checkpointed + # branch is the PR lineage boundary, including before publication. + if result.branch != repair.branch: + return self._save_terminal(repair, "BLOCKED", "bounded_scope_conflict", "Repair did not preserve the checkpointed branch.") repaired_result = replace( result, branch=result.branch or repair.branch, @@ -1062,6 +1237,20 @@ def _advance_after_repair_agent_result(self, repair: TransactionState, result: A ) if reviewed.terminal or reviewed.phase == "REPAIR_AGENT": return reviewed + # A repair before the first PR follows exactly the same host-owned + # publication gate as the original implementation. It must not fall + # through to delivery with a fictitious PR, nor grant repair itself + # publication authority. + if ( + reviewed.execution_mode == "MANAGED" + and reviewed.transaction_kind == "IMPLEMENTATION" + and reviewed.owner_authorized + and reviewed.pull_request is None + and reviewed_result.pull_request is None + ): + reviewed, reviewed_result = self._publish_first_implementation_pull_request(reviewed, reviewed_result) + if reviewed.terminal: + return reviewed try: evidence = self.repository.inspect(self.root) except RunnerError: @@ -1185,9 +1374,10 @@ def _record_verified_result_commit( return state if not (evidence.clean and evidence.branch == expected_branch and evidence.head_sha == result.commit_sha): return state - return self._append_verified_commit_evidence( + recorded = self._append_verified_commit_evidence( state, phase=phase, commit_sha=result.commit_sha, description=description, ) + return replace(recorded, last_verified_sha=result.commit_sha) @staticmethod def _validation_kind(command: str) -> str | None: @@ -1218,6 +1408,23 @@ def _validation_id(command: str, kind: str) -> str: return "dashboard_browser" return f"validation_{kind}" + @classmethod + def _validation_id_for_profile( + cls, command: str, kind: str, tier: object, + ) -> str: + """Map an observed command to the owning persisted control identity.""" + if not isinstance(tier, str): + return cls._validation_id(command, kind) + if kind == "format_or_diff": + return "git_diff_check" + if kind == "documentation_contract": + return "documentation_contract" + if kind == "tests": + return "repository_suite" if tier == "FULL" else "engineering_python" + if kind == "browser_e2e": + return cls._validation_id(command, kind) + return f"validation_{kind}" + def _invoke_provider_attempt_with_timing(self, state: TransactionState, prompt: str, *, repair: bool = False, quality: bool = False, local_validation: bool = False, attempt: int | None = None) -> AgentResult: """Run one provider attempt; recovery launch authority lives in storage.""" # Baseline capture is content-free and idempotent. Recovery progress @@ -1270,9 +1477,13 @@ def command_boundary(event: str, command_id: str, command: str, exit_code: int | if event == "started": kind = self._validation_kind(command) if kind is not None: - validation_id = self._validation_id(command, kind) + validation_id = self._validation_id_for_profile(command, kind, None) try: profile = load_validation_context(self.root, state.run_id, central_database=self.store.central_database) + validation_id = self._validation_id_for_profile( + command, kind, + profile.get("selected_validation_tier") if profile else None, + ) required = validation_id in set(profile["required_validation_controls"]) if profile else False started_at = datetime.now(timezone.utc).isoformat() record_validation_command_invocation( @@ -1501,6 +1712,17 @@ def _invoke_agent_with_timing(self, state: TransactionState, prompt: str, *, rep while True: recovery = self._recovery_state(state.run_id) if isinstance(recovery, dict) and recovery.get("state") == "RECOVERY_AVAILABLE": + if state.next_action == "publish_first_implementation_pull_request": + # Publication is externally observable. Unlike an + # implementation/repair turn it must never automatically + # launch attempt two: the publication gate performs exact + # branch/base/SHA readback before any later create action. + raise CodexInvocationError( + "First-publication acknowledgement is unresolved.", + "Publication recovery requires exact GitHub readback before another dispatch.", + next_action="NONE", terminal_condition="provider_turn_interrupted", + interruption_reason="publication_acknowledgement_unresolved", + ) precheck = self._provider_recovery_preflight(state) if precheck is not None: mark_precheck_failed( @@ -1546,11 +1768,10 @@ def _invoke_agent_with_timing(self, state: TransactionState, prompt: str, *, rep isinstance(recovery, dict) and recovery.get("state") == "RECOVERED" and recovery.get("lifecycle_phase") == state.phase - # EXECUTE_AGENT contains two distinct product dispatches: - # implementation and the later immutable PR publication. - # A recovered implementation result (necessarily no PR for - # a new run) must never be replayed as publication evidence. - and state.next_action != "publish_first_implementation_pull_request" + # EXECUTE_AGENT contains two distinct product dispatches. + # The recovered result remains phase-bound; the publication + # gate below verifies an immutable draft PR by readback + # before it can be accepted. ): replacement_id = recovery.get("replacement_invocation_id") if ( @@ -1573,7 +1794,7 @@ def _invoke_agent_with_timing(self, state: TransactionState, prompt: str, *, rep next_action="NONE", terminal_condition="provider_turn_interrupted", ) try: - return AgentResult( + recovered_result = AgentResult( terminal_state=str(payload["terminal_state"]), branch=payload.get("branch"), pull_request=payload.get("pull_request"), terminal_condition=str(payload.get("terminal_condition") or "repository_reconciled"), diagnostic=payload.get("diagnostic"), repository_path=payload.get("repository_path"), @@ -1587,6 +1808,18 @@ def _invoke_agent_with_timing(self, state: TransactionState, prompt: str, *, rep "Recovered provider result is invalid.", "Recovery result evidence cannot be consumed safely.", next_action="NONE", terminal_condition="provider_turn_interrupted", ) from error + # EXECUTE_AGENT has an implementation dispatch and a later, + # distinct first-publication dispatch. A recovered pre-PR + # implementation result has no PR and must not be replayed + # as the later publication result merely because both share + # that lifecycle phase. Conversely a recovered publication + # result must carry the durable PR identity for the gate to + # verify by provider readback without a second create. + if not ( + state.next_action == "publish_first_implementation_pull_request" + and recovered_result.pull_request is None + ): + return recovered_result if isinstance(recovery, dict) and recovery.get("state") in {"EXHAUSTED", "PRECHECK_FAILED", "AMBIGUOUS"}: raise CodexInvocationError( "Provider interruption recovery cannot continue.", "Recovery budget is exhausted or recovery evidence is unsafe.", @@ -1733,6 +1966,7 @@ def _run_local_repository_validation( description="local_repository_validation_commit_verified", ) self._persist_agent_usage(validation.run_id) + validation_context = self._validation_context(validation) except ProviderReadinessBlocked as blocked: return blocked.state, implementation except CodexInvocationError as error: @@ -1755,12 +1989,33 @@ def _run_local_repository_validation( validation, "BLOCKED", "implementation_pr_before_assurance", "Read-only local validation returned a pull request before mandatory assurance.", ), implementation - if result.terminal_state == "COMPLETE" and result.validation_evidence and not self._has_failed_validation_evidence(result): + if ( + result.terminal_state == "COMPLETE" + and result.validation_evidence + and not self._has_failed_validation_evidence(result) + and _required_validation_controls_pass(validation, validation_context) + ): validation = self._record_local_validation_audit(validation, result=result, outcome="validated", profile=profile) return validation, replace( result, branch=branch, pull_request=None, validation_evidence=implementation.validation_evidence + result.validation_evidence, ) + if result.terminal_state == "COMPLETE" and not self._has_failed_validation_evidence(result): + controls = validation_context.get("controls", {}) if validation_context else {} + required = validation_context.get("required_validation_controls", ()) if validation_context else () + if not any( + isinstance(controls.get(control), dict) + and controls[control].get("currentness") == validation.repair_iterations + and controls[control].get("result") == "FAIL" + for control in required + ): + validation = self._record_local_validation_audit( + validation, result=result, outcome="agent_failed", profile=profile, + ) + return self._save_terminal( + validation, "BLOCKED", "required_validation_unresolved", + "Local validation did not produce current successful terminal evidence for every required control.", + ), implementation validation = self._record_local_validation_audit(validation, result=result, outcome="validation_failed", profile=profile) if self._is_environmental_validation_instability(result): return self._save_terminal( @@ -1798,15 +2053,32 @@ def _run_quality_assurance( profile_version = f"validation-profile@{VALIDATION_PROFILE_VERSION}" criteria = Path(quality.prompt_path).read_text(encoding="utf-8") criteria_digest = "sha256:" + hashlib.sha256(criteria.encode("utf-8")).hexdigest() + try: + validation_context = load_validation_context( + self.root, quality.run_id, central_database=self.store.central_database, + ) + except EngineeringStorageError: + validation_context = None + validation_profile_digest = _validation_profile_digest(validation_context) # Candidate identity and policy identity are intentionally independent: # a source revision must not silently select or weaken its own policy. profile_digest = "sha256:" + hashlib.sha256( - json.dumps({"baseline": profile_version, "criteria_digest": criteria_digest}, sort_keys=True).encode("utf-8") + json.dumps({ + "baseline": profile_version, + "criteria_digest": criteria_digest, + "validation_profile_digest": validation_profile_digest, + }, sort_keys=True).encode("utf-8") ).hexdigest() - quality = replace(quality, assurance_profile={ + assurance_profile = { "version": profile_version, "digest": profile_digest, "candidate_sha": candidate.head_sha, "criteria_digest": criteria_digest, - }) + } + # Genesis has no host-owned local validation profile. Preserve the + # valid legacy assurance shape instead of persisting an explicit null, + # which cannot survive the durable checkpoint schema round-trip. + if validation_profile_digest is not None: + assurance_profile["validation_profile_digest"] = validation_profile_digest + quality = replace(quality, assurance_profile=assurance_profile) self.store.save(quality) write_live_status(self.root, quality, quality.next_action) evidence = ReviewerEvidence.from_repository(quality.run_id, quality.execution_mode, candidate) @@ -1918,24 +2190,19 @@ def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> Repos @staticmethod def _current_assurance_passes(state: TransactionState) -> bool: """Require the two mandatory reviews for this exact profile/candidate.""" - profile = state.assurance_profile - if not isinstance(profile, dict): - return False - candidate, digest = profile.get("candidate_sha"), profile.get("digest") - if not isinstance(candidate, str) or not isinstance(digest, str): - return False - current = [ - review for review in state.assurance_reviews - if review.get("candidate_sha") == candidate and review.get("profile_digest") == digest - ] - return all( - any(review.get("reviewer") == role and review.get("status") == "PASS" for review in current) - for role in ("quality", "security") - ) and not any( - finding.get("blocking") and finding.get("disposition") == "OPEN" - for review in current for finding in review.get("findings", []) - if isinstance(finding, dict) - ) + return _has_current_assurance_evidence(state) + + def _validation_context(self, state: TransactionState) -> dict[str, object] | None: + try: + return load_validation_context( + self.root, state.run_id, central_database=self.store.central_database, + ) + except EngineeringStorageError: + return None + + def _current_local_validation_passes(self, state: TransactionState) -> bool: + """Require current passing local validation before first publication.""" + return _has_current_local_validation_evidence(state, self._validation_context(state)) def _publish_first_implementation_pull_request( self, state: TransactionState, implementation: AgentResult, @@ -1947,8 +2214,16 @@ def _publish_first_implementation_pull_request( the already reviewed candidate; the host pins its SHA before and after the PR hand-off. """ - if state.execution_mode == "GENESIS" or state.pull_request or implementation.pull_request: + recovered_publication = ( + state.next_action == "publish_first_implementation_pull_request" + and implementation.pull_request is not None + ) + if state.execution_mode == "GENESIS" or state.pull_request or ( + implementation.pull_request and not recovered_publication + ): return state, implementation + if not self._current_local_validation_passes(state): + return self._save_terminal(state, "BLOCKED", "implementation_publication_assurance_required", "First implementation PR publication requires current passing local validation for the reviewed candidate."), implementation if not self._current_assurance_passes(state): return self._save_terminal(state, "BLOCKED", "implementation_publication_assurance_required", "First implementation PR publication requires current passing quality and security assurance."), implementation try: @@ -1960,20 +2235,81 @@ def _publish_first_implementation_pull_request( return self._save_terminal(state, "BLOCKED", "implementation_publication_candidate_changed", "The reviewed candidate changed before first PR publication."), implementation publication = replace(state, phase="EXECUTE_AGENT", next_action="publish_first_implementation_pull_request") self.store.save(publication) - prompt = assemble_prompt(Path(publication.prompt_path), publication, managed_target=self.root) + """ + prompt = assemble_prompt( + Path(publication.prompt_path), publication, managed_target=self.root, + validation_context=self._validation_context(publication), + ) + """ First implementation pull-request publication gate: - The Execution Host has already recorded passing local validation plus independent quality and security assurance for the exact current candidate. - Do not edit files, index, commits, branch, tests, configuration, or evidence. Do not merge, release, or change repository settings. - Create exactly one draft implementation pull request for the existing bounded branch and current HEAD. Return that existing branch, the GitHub pull-request number and the unchanged current commit SHA. """ - try: - published = self._invoke_agent_with_timing(publication, prompt) - publication = self._record_agent_execution_time(publication) - except (CodexInvocationError, ProviderReadinessBlocked) as error: - if isinstance(error, ProviderReadinessBlocked): - return error.state, implementation - return self._terminalize_provider_invocation_error(publication, error), implementation + if recovered_publication: + # A lost acknowledgement is reconciled from its durable provider + # result. Never issue a second PR-create turn for that run. + published = implementation + else: + # A process can stop after GitHub accepted the create but before + # the provider result became durable. Reconcile the exact pinned + # identity before every create dispatch; never adopt by branch + # name alone and never replay while a mismatching PR is visible. + try: + existing = self.github.pull_request_for_head_branch(before.branch) + except RunnerError: + return self._save_terminal( + publication, "BLOCKED", "implementation_publication_evidence_invalid", + "Pre-create pull-request readback was unavailable or ambiguous; first publication was not dispatched.", + ), implementation + if existing is not None: + if not ( + existing.state == "OPEN" + and existing.is_draft + and existing.head_branch == before.branch + and existing.base_branch == "main" + and existing.head_sha == before.head_sha + ): + return self._save_terminal( + publication, "BLOCKED", "implementation_publication_evidence_invalid", + "Existing pull-request readback does not match the reviewed publication candidate.", + ), implementation + published = AgentResult( + "COMPLETE", branch=before.branch, + pull_request=existing.number, commit_sha=before.head_sha, + ) + else: + try: + published = self._invoke_agent_with_timing(publication, prompt) + publication = self._record_agent_execution_time(publication) + except (CodexInvocationError, ProviderReadinessBlocked) as error: + if isinstance(error, ProviderReadinessBlocked): + return error.state, implementation + # The provider may have created the draft PR and lost only + # its acknowledgement. Before terminalizing (and before any + # future provider retry), reconcile exactly one readback for + # this already-pinned branch, base, and candidate SHA. A + # branch match alone is deliberately never enough. + try: + recovered = self.github.pull_request_for_head_branch(before.branch) + except RunnerError: + return self._save_terminal( + publication, "BLOCKED", "implementation_publication_evidence_invalid", + "Pull-request acknowledgement readback was unavailable or ambiguous; publication must be reconciled before any retry.", + ), implementation + if ( + recovered is not None + and recovered.state == "OPEN" + and recovered.is_draft + and recovered.head_branch == before.branch + and recovered.base_branch == "main" + and recovered.head_sha == before.head_sha + ): + published = AgentResult( + "COMPLETE", branch=before.branch, + pull_request=recovered.number, commit_sha=before.head_sha, + ) + else: + return self._terminalize_provider_invocation_error(publication, error), implementation try: after = self.repository.inspect(self.root) except RunnerError: @@ -1983,6 +2319,16 @@ def _publish_first_implementation_pull_request( if not published.pull_request: failures.append("missing_pull_request") if published.branch != before.branch: failures.append("branch_mismatch") if published.commit_sha != before.head_sha: failures.append("candidate_sha_mismatch") + try: + pull_request = self.github.pull_request(published.pull_request) if published.pull_request else None + except RunnerError: + pull_request = None + if pull_request is None: failures.append("pull_request_unavailable") + elif pull_request.state != "OPEN": failures.append("pull_request_not_open") + elif not pull_request.is_draft: failures.append("pull_request_not_draft") + elif pull_request.head_branch != before.branch: failures.append("pull_request_branch_mismatch") + elif pull_request.base_branch != "main": failures.append("pull_request_base_mismatch") + elif pull_request.head_sha != before.head_sha: failures.append("pull_request_candidate_mismatch") if after is None: failures.append("candidate_unavailable_after_publication") elif not after.clean: failures.append("candidate_dirty_after_publication") elif after.branch != before.branch: failures.append("branch_changed_after_publication") @@ -2104,6 +2450,18 @@ def _advance_after_recovered_provider_result( ) -> TransactionState: """Route a validated durable result to its existing phase handler.""" if lifecycle_phase == "EXECUTE_AGENT": + if ( + state.next_action == "publish_first_implementation_pull_request" + and result.pull_request is not None + ): + # A durable publication acknowledgement is not a fresh + # implementation result. Reconcile its exact draft identity + # through the existing publication gate; never re-run local + # validation/reviews or create another PR. + published, publication_result = self._publish_first_implementation_pull_request(state, result) + if published.terminal: + return published + return self._continue_after_quality_control(published, publication_result, evidence) return self._advance_after_primary_agent_result(state, result, evidence) if lifecycle_phase == "QUALITY_CONTROL_AGENT": # The assurance phase has no mutating provider invocation to @@ -2123,6 +2481,7 @@ def run( resume: bool = False, owner_authorized: bool = False, transaction_kind: str = "IMPLEMENTATION", + candidate_adoption: tuple[str, str] | None = None, ) -> TransactionState: # Private helpers remain directly testable, while every public runner # invocation enforces the admission boundary before provider dispatch. @@ -2225,10 +2584,42 @@ def run( execution_mode=context.execution_mode, action_intent=context.action_intent, ) + if candidate_adoption is not None: + branch, candidate_sha = candidate_adoption + if not ( + context.execution_mode == "MANAGED" + and transaction_kind == "IMPLEMENTATION" + and owner_authorized + and re.fullmatch(r"codex/[A-Za-z0-9._/-]{1,120}", branch) + and re.fullmatch(r"[0-9a-f]{40}", candidate_sha) + ): + raise RunnerError("candidate adoption is not authorised for this transaction") + # The typed submission pins the initial adoption candidate. A + # resumed run may already have a newer, host-verified repair + # candidate; never overwrite that durable lineage with the input + # SHA on resume. + if state.branch not in {None, branch}: + return self._save_terminal( + state, "BLOCKED", "candidate_adoption_identity_invalid", + "Candidate adoption branch conflicts with the durable run branch.", + ) + state = replace( + state, + branch=branch, + last_verified_sha=state.last_verified_sha or candidate_sha, + ) context = replace(context, run_id=state.run_id) passive_pr_wait = state.pull_request is not None and state.phase in { "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE" } + publication_resume = ( + resume + and state.execution_mode == "MANAGED" + and state.transaction_kind == "IMPLEMENTATION" + and state.phase == "EXECUTE_AGENT" + and state.next_action == "publish_first_implementation_pull_request" + and state.pull_request is None + ) state = self._provider_readiness_gate( state, require_codex=not passive_pr_wait, @@ -2236,6 +2627,17 @@ def run( ) if state.next_action == "provider_auth_repair_required": return state + # A provider-readiness repair restores the checkpointed action only + # inside the gate above. Recompute this route from that trusted state + # so an auth recovery cannot fall through to main synchronization. + publication_resume = ( + resume + and state.execution_mode == "MANAGED" + and state.transaction_kind == "IMPLEMENTATION" + and state.phase == "EXECUTE_AGENT" + and state.next_action == "publish_first_implementation_pull_request" + and state.pull_request is None + ) self.transaction = ExecutionTransaction( state=state, target_repository=context.target_repository or self.root, @@ -2386,7 +2788,11 @@ def run( # Synchronization is a host-owned admission step. Do it while this # run owns the lease so agents never race each other for index.lock, # and so the bounded retry policy in the repository client is used. - if context.execution_mode == "MANAGED": + if ( + context.execution_mode == "MANAGED" + and candidate_adoption is None + and not publication_resume + ): try: self.repository.synchronize_main(self.root) evidence = self.repository.inspect(self.root) @@ -2397,6 +2803,31 @@ def run( "repository_synchronization", f"Repository synchronization failed: {redact_diagnostic(str(error))}", ) + elif candidate_adoption is not None: + branch, candidate_sha = candidate_adoption + expected_sha = state.last_verified_sha or candidate_sha + evidence = self.repository.inspect(self.root) + if not (evidence.clean and evidence.branch == branch and evidence.head_sha == expected_sha): + return self._save_terminal( + state, "BLOCKED", "candidate_adoption_identity_invalid", + "Candidate adoption requires the exact clean checkpointed branch and SHA.", + ) + elif publication_resume: + evidence = self.repository.inspect(self.root) + if not ( + evidence.clean + and evidence.branch == state.branch + and evidence.head_sha == state.last_verified_sha + ): + return self._save_terminal( + state, "BLOCKED", "implementation_publication_candidate_changed", + "Publication resume requires the exact clean checkpointed branch and candidate SHA.", + ) + if ( + context.execution_mode == "MANAGED" + and candidate_adoption is None + and not publication_resume + ): # The watcher checked the target before claim. Re-check the exact # checkout after the host-owned synchronization while the run lease # is held: an operator/worktree race must never reach reviewers, @@ -2421,6 +2852,18 @@ def run( state = self._bind_validation_only_profile(state, producer_context) if state.terminal: return state + if publication_resume: + resumed = AgentResult( + "COMPLETE", branch=state.branch, commit_sha=state.last_verified_sha, + ) + published, published_result = self._publish_first_implementation_pull_request( + state, resumed, + ) + if published.terminal: + return published + return self._continue_after_quality_control( + published, published_result, self.repository.inspect(self.root), + ) if context.execution_mode == "MANAGED": self._managed_action(state, "IMPLEMENTATION" if state.action_intent == "MUTATING_DELIVERY" else "VALIDATION_ONLY") self._provider_dispatch_telemetry = { @@ -2518,6 +2961,18 @@ def run( if not required or not all(result == "PASS" for result in results): return self._save_terminal(state, "BLOCKED", "required_validation_unresolved", "Required validation controls do not have authoritative PASS evidence.") return self._poll(state, AgentResult("COMPLETE")) + if candidate_adoption is not None: + adopted = AgentResult("COMPLETE", branch=state.branch, commit_sha=state.last_verified_sha) + validated, validation_result = self._run_local_repository_validation(state, adopted) + if validated.terminal: + return validated + reviewed, reviewed_result = self._run_quality_assurance(validated, validation_result) + if reviewed.terminal or reviewed.phase == "REPAIR_AGENT": + return reviewed + published, published_result = self._publish_first_implementation_pull_request(reviewed, reviewed_result) + if published.terminal: + return published + return self._continue_after_quality_control(published, published_result, self.repository.inspect(self.root)) try: if hasattr(self.agent, "set_activity_callback"): self.agent.set_activity_callback( @@ -2593,6 +3048,14 @@ def run( return self._terminalize_provider_invocation_error(state, error) return self._advance_after_primary_agent_result(state, result, evidence) + def run_adopted_candidate(self, prompt_path: Path, *, run_id: str, resume: bool, + branch: str, candidate_sha: str) -> TransactionState: + """Run one host-owned assurance/publication chain for an existing candidate.""" + return self.run( + prompt_path, run_id=run_id, resume=resume, owner_authorized=True, + transaction_kind="IMPLEMENTATION", candidate_adoption=(branch, candidate_sha), + ) + def _active_genesis_transaction(self, target: Path, run_id: str) -> str | None: """Return another active Genesis run that owns the same local workspace.""" for checkpoint_id in self.store.run_ids(): diff --git a/src/engineering_platform/parity_context.py b/src/engineering_platform/parity_context.py index 2754c1ae..42409c4e 100644 --- a/src/engineering_platform/parity_context.py +++ b/src/engineering_platform/parity_context.py @@ -8,6 +8,7 @@ from dataclasses import dataclass import json from pathlib import Path +import re import sqlite3 from typing import Literal @@ -50,6 +51,31 @@ class HistoricalCandidate: constraints: dict[str, object] execution_mode: ExecutionMode + def candidate_adoption(self) -> tuple[str, str] | None: + """Return the one explicitly authorised existing-candidate binding. + + This is deliberately a typed, exact constraint rather than an + instruction parsed from the free prompt. A submission may not turn + ordinary Managed work into adoption by merely mentioning a branch. + """ + value = self.constraints.get("managed_candidate_adoption") + if value is None: + return None + if not isinstance(value, dict) or set(value) != {"branch", "candidate_sha", "owner_authorized"}: + raise ParityContextError("CANDIDATE_ADOPTION_INVALID") + branch, sha, authorized = value.get("branch"), value.get("candidate_sha"), value.get("owner_authorized") + if ( + not isinstance(branch, str) + or not re.fullmatch(r"codex/[A-Za-z0-9._/-]{1,120}", branch) + or not isinstance(sha, str) + or not re.fullmatch(r"[0-9a-f]{40}", sha) + or authorized is not True + ): + raise ParityContextError("CANDIDATE_ADOPTION_INVALID") + if self.execution_mode != "MANAGED": + raise ParityContextError("CANDIDATE_ADOPTION_MODE_INVALID") + return branch, sha + def producer_envelope(self) -> str: """Return the existing validated Producer Submission Envelope shape.""" producer: dict[str, object] = {"id": self.producer_id, "type": self.producer_type} diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index 52775145..661d390d 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -122,6 +122,9 @@ class Runner(Protocol): def run(self, prompt_path: Path, run_id: str | None = None, resume: bool = False, owner_authorized: bool = False, transaction_kind: str = "IMPLEMENTATION") -> TransactionState: ... + def run_adopted_candidate(self, prompt_path: Path, *, run_id: str, resume: bool, + branch: str, candidate_sha: str) -> TransactionState: ... + RunnerFactory = Callable[[Path], Runner] @@ -356,7 +359,11 @@ def _persist_historical_input(repository_root: Path, candidate: HistoricalCandid fresh_submission=True, retry_parent_run_id=None, resume_parent_run_id=None, recorded_at=now, ) host = execute_host_preflight(repository_root, run_id=run_id) - workspace = execute_workspace_preflight(repository_root, candidate.prompt, run_id=run_id) + adoption = candidate.candidate_adoption() + workspace = execute_workspace_preflight( + repository_root, candidate.prompt, run_id=run_id, + managed_candidate_branch=adoption[0] if adoption else None, + ) capability = execute_capability_preflight(repository_root, candidate.prompt, run_id=run_id) decision, _ = _record_provider_free_admission( repository_root, run_id=run_id, submission_id=candidate.submission_id, @@ -507,10 +514,20 @@ def dispatch(self, submission_id: str) -> DispatchReceipt: if not duplicate or not prompt.is_file(): self._persist_historical_input(repository_root, candidate, run_id, prompt) runner = self.runner_factory(repository_root) - state = runner.run( - prompt, run_id=run_id, resume=duplicate, - owner_authorized=candidate.execution_mode == "MANAGED", - ) + adoption = candidate.candidate_adoption() + if adoption is None: + state = runner.run( + prompt, run_id=run_id, resume=duplicate, + owner_authorized=candidate.execution_mode == "MANAGED", + ) + else: + method = getattr(runner, "run_adopted_candidate", None) + if not callable(method): + raise ParityLifecycleDispatchError("CANDIDATE_ADOPTION_RUNTIME_UNAVAILABLE") + state = method( + prompt, run_id=run_id, resume=duplicate, + branch=adoption[0], candidate_sha=adoption[1], + ) # Report/history indexing is execution evidence too. Keep it # inside the explicit CENTRAL context; a terminal projection # must never reopen the repository-local database. diff --git a/src/engineering_platform/platform_version.py b/src/engineering_platform/platform_version.py index d788b079..74bcf316 100644 --- a/src/engineering_platform/platform_version.py +++ b/src/engineering_platform/platform_version.py @@ -12,7 +12,7 @@ SEMVER = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$") CONTRACT = re.compile(r"^(\d{4})\.(0[1-9]|1[0-2])$") -CURRENT_PLATFORM_VERSION = "2.3.2" +CURRENT_PLATFORM_VERSION = "2.3.3" MANIFEST_FIELDS = frozenset( { "platform_version", diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index b143d237..a30c0246 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -10,6 +10,7 @@ import os import re import subprocess +import sys import time from .capability_review import MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, ReviewerResult @@ -19,10 +20,14 @@ class DeterministicQualificationAgent: def __init__(self) -> None: self._process_callback = None + self._command_callback = None def set_process_callback(self, callback: object) -> None: self._process_callback = callback + def set_command_callback(self, callback: object) -> None: + self._command_callback = callback + def wait_for_controlled_interruption_arm(self, _root: Path, state: object) -> None: """Offer the installed recovery E2E one bounded, non-production arm window.""" ready = os.environ.get("EP_QUALIFICATION_CONTROL_ARM_READY_FILE") @@ -155,14 +160,35 @@ def validate(self, root: Path, prompt: str) -> AgentResult: """Qualification validation is explicitly non-mutating. The installed test composition exercises the same host gate as the - production adapter but cannot manufacture a commit or PR while that - gate is active. + production adapter and emits the same command-boundary receipts. Its + two FULL-profile controls are executed against only the isolated + fixture; it cannot manufacture a commit or PR while this gate is + active. """ branch = subprocess.run(("git", "-C", str(root), "branch", "--show-current"), check=True, text=True, capture_output=True).stdout.strip() sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() + controls = ( + ("git diff --check", ("git", "diff", "--check")), + ("python3 -m unittest discover", (sys.executable, "-m", "unittest", "discover")), + ) + evidence: list[dict[str, str]] = [] + failed = False + for ordinal, (identity, command) in enumerate(controls, start=1): + command_id = f"deterministic-validation-{ordinal}" + if callable(self._command_callback): + self._command_callback("started", command_id, identity) + completed = subprocess.run( + command, cwd=root, text=True, capture_output=True, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, + ) # nosec B603 + if callable(self._command_callback): + self._command_callback("completed", command_id, identity, completed.returncode) + passed = completed.returncode == 0 + failed = failed or not passed + evidence.append({"command": identity, "result": "passed" if passed else "failed"}) return AgentResult( - "COMPLETE", branch=branch, commit_sha=sha, - validation_evidence=({"command": "deterministic installed validation", "result": "passed"},), + "FAILED" if failed else "COMPLETE", branch=branch, commit_sha=sha, + validation_evidence=tuple(evidence), ) def review(self, _root: Path, selection: object, _objective: str, evidence: object = None) -> ReviewerResult: return ReviewerResult( @@ -179,8 +205,14 @@ def __init__(self, root: Path) -> None: def pull_request(self, number: int) -> PullRequestEvidence: self.calls += 1 sha = subprocess.run(("git", "-C", str(self.root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() - if self.calls == 1: - return PullRequestEvidence(number, "OPEN", True, True, head_branch="qualification-managed", base_branch="main") + # The first readback binds publication to the reviewed draft. The + # host reads it again before readying it to reject historical merged + # evidence; only the subsequent normal poll models the fixture merge. + if self.calls <= 2: + return PullRequestEvidence( + number, "OPEN", True, True, is_draft=True, + head_branch="qualification-managed", base_branch="main", head_sha=sha, + ) return PullRequestEvidence(number, "MERGED", True, True, merge_commit=sha, head_branch="qualification-managed", base_branch="main") def pull_request_for_head_branch(self, _branch: str): return None diff --git a/src/engineering_platform/templates/workspace-config.json b/src/engineering_platform/templates/workspace-config.json index dcac8c9c..067a9cdf 100644 --- a/src/engineering_platform/templates/workspace-config.json +++ b/src/engineering_platform/templates/workspace-config.json @@ -3,7 +3,7 @@ "platform": { "id": "engineering-platform", "name": "Engineering Platform", - "version": "2.3.2", + "version": "2.3.3", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 diff --git a/src/engineering_platform/workspace_preflight.py b/src/engineering_platform/workspace_preflight.py index 67101454..0d31d755 100644 --- a/src/engineering_platform/workspace_preflight.py +++ b/src/engineering_platform/workspace_preflight.py @@ -151,7 +151,8 @@ def _persist(root: Path, result: WorkspacePreflightResult, run_id: str | None) - Path(temporary).unlink(missing_ok=True) -def execute(root: Path, prompt: str, *, run_id: str | None = None) -> WorkspacePreflightResult: +def execute(root: Path, prompt: str, *, run_id: str | None = None, + managed_candidate_branch: str | None = None) -> WorkspacePreflightResult: """Run Level 2 workspace checks without mutating the selected repository.""" started = monotonic() timestamp = datetime.now(timezone.utc).isoformat() @@ -230,7 +231,7 @@ def execute(root: Path, prompt: str, *, run_id: str | None = None) -> WorkspaceP if mode == "GENESIS": checks.append(_check("genesis_local_repository", branch != "detached", "Genesis target is a local repository." if branch != "detached" else "Genesis target has no active local branch.", "Select a local repository with an active branch.")) else: - expected = configuration.workspace.default_branch if configuration else "main" + expected = managed_candidate_branch or (configuration.workspace.default_branch if configuration else "main") checks.append(_check("managed_expected_branch", branch == expected, "Managed target is on the expected branch." if branch == expected else f"Managed target is not on the expected branch {expected}.", f"Switch the repository to {expected} before submitting work.")) remote = _git(target, "remote", "get-url", "origin") remote_valid = remote.returncode == 0 and bool(remote.stdout.strip()) diff --git a/tests/engineering/test_build_platform_wheel.py b/tests/engineering/test_build_platform_wheel.py new file mode 100644 index 00000000..81cb6ffa --- /dev/null +++ b/tests/engineering/test_build_platform_wheel.py @@ -0,0 +1,68 @@ +"""Regression tests for the committed-candidate wheel source boundary.""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path +import subprocess +import sys +import tempfile +import unittest + + +def _builder_module() -> object: + qualification = Path(__file__).parents[2] / "tools" / "qualification" + path = qualification / "build_platform_wheel.py" + specification = importlib.util.spec_from_file_location("build_platform_wheel", path) + if specification is None or specification.loader is None: + raise RuntimeError("wheel builder module is unavailable") + module = importlib.util.module_from_spec(specification) + sys.path.insert(0, str(qualification)) + try: + specification.loader.exec_module(module) + finally: + sys.path.pop(0) + return module + + +class BuildPlatformWheelTest(unittest.TestCase): + def test_committed_snapshot_excludes_ignored_build_residue(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) / "repository" + destination = Path(temporary) / "snapshot" + root.mkdir() + subprocess.run(("git", "init", "-q", "-b", "main", str(root)), check=True) + subprocess.run(("git", "-C", str(root), "config", "user.email", "qualification@example.invalid"), check=True) + subprocess.run(("git", "-C", str(root), "config", "user.name", "Qualification"), check=True) + (root / ".gitignore").write_text("build/\n", encoding="utf-8") + (root / "tracked.txt").write_text("candidate\n", encoding="utf-8") + subprocess.run(("git", "-C", str(root), "add", ".gitignore", "tracked.txt"), check=True) + subprocess.run(("git", "-C", str(root), "commit", "-qm", "candidate"), check=True) + stale = root / "build" / "lib" / "retired.py" + stale.parent.mkdir(parents=True) + stale.write_text("retired = True\n", encoding="utf-8") + + _builder_module()._extract_committed_source(root, destination) + + self.assertEqual((destination / "tracked.txt").read_text(encoding="utf-8"), "candidate\n") + self.assertFalse((destination / "build").exists()) + + def test_committed_snapshot_rejects_tracked_mutations(self) -> None: + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) / "repository" + root.mkdir() + subprocess.run(("git", "init", "-q", "-b", "main", str(root)), check=True) + subprocess.run(("git", "-C", str(root), "config", "user.email", "qualification@example.invalid"), check=True) + subprocess.run(("git", "-C", str(root), "config", "user.name", "Qualification"), check=True) + candidate = root / "candidate.txt" + candidate.write_text("committed\n", encoding="utf-8") + subprocess.run(("git", "-C", str(root), "add", "candidate.txt"), check=True) + subprocess.run(("git", "-C", str(root), "commit", "-qm", "candidate"), check=True) + candidate.write_text("changed\n", encoding="utf-8") + + with self.assertRaisesRegex(RuntimeError, "uncommitted tracked changes"): + _builder_module()._extract_committed_source(root, Path(temporary) / "snapshot") + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/engineering/test_deterministic_execution_e2e.py b/tests/engineering/test_deterministic_execution_e2e.py index 3215aa7c..9c0ba910 100644 --- a/tests/engineering/test_deterministic_execution_e2e.py +++ b/tests/engineering/test_deterministic_execution_e2e.py @@ -9,6 +9,9 @@ import tempfile import unittest +from engineering_platform.execution_models import AgentResult +from engineering_platform.qualification_runtime import DeterministicQualificationAgent + def _qualification_module() -> object: path = Path(__file__).parents[2] / "tools" / "qualification" / "p_deterministic_execution_e2e.py" @@ -57,6 +60,46 @@ def test_isolated_runtime_rejects_an_unexpected_server_configuration_shape(self) with self.assertRaisesRegex(RuntimeError, "QUALIFICATION_SERVER_CONFIGURATION_INVALID"): self.module.configure_deterministic_runtime(self.data, self.root) + def test_deterministic_validation_executes_and_reports_full_profile_controls(self) -> None: + subprocess.run(("git", "init", "-q", "-b", "main", str(self.root)), check=True) + subprocess.run(("git", "-C", str(self.root), "config", "user.email", "qualification@example.invalid"), check=True) + subprocess.run(("git", "-C", str(self.root), "config", "user.name", "Qualification"), check=True) + (self.root / "README.md").write_text("# Qualification\n", encoding="utf-8") + (self.root / "test_qualification.py").write_text( + "import unittest\n\n" + "class QualificationTest(unittest.TestCase):\n" + " def test_fixture(self):\n" + " self.assertTrue(True)\n", + encoding="utf-8", + ) + subprocess.run(("git", "-C", str(self.root), "add", "README.md", "test_qualification.py"), check=True) + subprocess.run(("git", "-C", str(self.root), "commit", "-qm", "initial"), check=True) + agent = DeterministicQualificationAgent() + events: list[tuple[object, ...]] = [] + agent.set_command_callback(lambda *event: events.append(event)) + + result = agent.validate(self.root, "host-owned local validation") + + self.assertIsInstance(result, AgentResult) + self.assertEqual(result.terminal_state, "COMPLETE") + self.assertEqual(result.validation_evidence, ( + {"command": "git diff --check", "result": "passed"}, + {"command": "python3 -m unittest discover", "result": "passed"}, + )) + self.assertEqual(events, [ + ("started", "deterministic-validation-1", "git diff --check"), + ("completed", "deterministic-validation-1", "git diff --check", 0), + ("started", "deterministic-validation-2", "python3 -m unittest discover"), + ("completed", "deterministic-validation-2", "python3 -m unittest discover", 0), + ]) + self.assertEqual( + subprocess.run( + ("git", "-C", str(self.root), "status", "--porcelain"), + check=True, text=True, capture_output=True, + ).stdout, + "", + ) + if __name__ == "__main__": unittest.main() diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 86d9c9f5..5af344b3 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1,5 +1,6 @@ from __future__ import annotations +from dataclasses import replace from pathlib import Path import json import os @@ -128,7 +129,7 @@ def cleanup_transaction(self, root: Path, branches: tuple[str | None, ...]) -> s class FakeGitHub: - def __init__(self, responses: list[PullRequestEvidence | RunnerError], *, branch_response: PullRequestEvidence | None = None) -> None: + def __init__(self, responses: list[PullRequestEvidence | RunnerError], *, branch_response: PullRequestEvidence | RunnerError | None = None) -> None: self.responses = responses self.branch_response = branch_response self.calls = 0 @@ -146,6 +147,8 @@ def pull_request(self, number: int) -> PullRequestEvidence: def pull_request_for_head_branch(self, branch: str) -> PullRequestEvidence | None: self.branch_calls.append(branch) + if isinstance(self.branch_response, RunnerError): + raise self.branch_response return self.branch_response def ready(self, number: int) -> None: @@ -424,14 +427,25 @@ class DeliveryAgent(FakeAgent): def __init__(self) -> None: super().__init__(AgentResult("COMPLETE")) self.pr_create_calls = 0 + self.command_callback: object | None = None + + def set_command_callback(self, callback: object) -> None: + self.command_callback = callback def invoke(self, root: Path, prompt_text: str) -> AgentResult: self.roots.append(root) self.prompts.append(prompt_text) if "Local repository validation gate" in prompt_text: + if callable(self.command_callback): + for ordinal, command in enumerate( + ("git diff --check", "python -m unittest discover"), start=1, + ): + command_id = f"restart-validation-{ordinal}" + self.command_callback("started", command_id, command) + self.command_callback("completed", command_id, command, 0) return AgentResult( "COMPLETE", branch, commit_sha=commit, - validation_evidence=({"command": "canonical suite", "result": "passed"},), + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), ) if "First implementation pull-request publication gate" in prompt_text: self.pr_create_calls += 1 @@ -446,7 +460,8 @@ def invoke(self, root: Path, prompt_text: str) -> AgentResult: agent = DeliveryAgent() github = FakeGitHub([ PullRequestEvidence( - 701, "OPEN", True, True, head_branch=branch, base_branch="main" + 701, "OPEN", True, True, is_draft=True, + head_branch=branch, base_branch="main", head_sha=commit, ) ]) first_host = EngineeringRunner( @@ -1665,6 +1680,131 @@ def tearDown(self) -> None: else: os.environ[key] = value + def _record_passing_validation_profile( + self, run_id: str, *, currentness: int = 0, + ) -> str: + """Persist real terminal receipts for one compact required profile.""" + started_at = "2026-08-29T00:00:00+00:00" + completed_at = "2026-08-29T00:00:01+00:00" + record_validation_profile( + self.root, run_id=run_id, selected_validation_tier="FULL", + validation_profile_version=execution_host.VALIDATION_PROFILE_VERSION, + required_validation_controls=("repository_suite",), + recorded_at=started_at, + ) + record_validation_command_invocation( + self.root, run_id=run_id, validation_id="repository_suite", + command_id=f"{run_id}-repository-suite", category="repository", + control_identity="python3 -m unittest discover", required_for_profile=True, + started_at=started_at, currentness=currentness, + ) + record_validation_command_terminal( + self.root, run_id=run_id, command_id=f"{run_id}-repository-suite", + completed_at=completed_at, exit_code=0, + ) + context = load_validation_context(self.root, run_id) + assert context is not None + digest = execution_host._validation_profile_digest(context) + assert digest is not None + return digest + + def test_phase_policy_helpers_fail_closed_for_incomplete_trusted_state(self) -> None: + sha = "a" * 40 + context: dict[str, object] = { + "selected_validation_tier": "FULL", + "validation_profile_version": execution_host.VALIDATION_PROFILE_VERSION, + "profile_reference": "validation-profile-registry:FULL@1.0", + "profile_selection_source": "diff_classification", + "required_validation_controls": ("repository_suite",), + "control_bindings": ({"validation_id": "repository_suite"},), + "controls": { + "repository_suite": { + "required_for_profile": True, + "execution_status": "EXECUTED", + "result": "PASS", + "currentness": 0, + "exit_code": 0, + "started_at": "2026-09-10T00:00:00+00:00", + "ended_at": "2026-09-10T00:00:01+00:00", + }, + }, + } + validation_digest = execution_host._validation_profile_digest(context) + assert validation_digest is not None + assurance_digest = "sha256:" + "b" * 64 + profile = { + "version": "validation-profile@1.0", + "digest": assurance_digest, + "candidate_sha": sha, + "criteria_digest": "sha256:" + "c" * 64, + "validation_profile_digest": validation_digest, + } + reviews = tuple( + { + "reviewer": role, + "status": "PASS", + "candidate_sha": sha, + "profile_digest": assurance_digest, + "invocation_id": role, + "findings": [], + } + for role in ("quality", "security") + ) + state = TransactionState( + "phase-policy", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + owner_authorized=True, execution_mode="MANAGED", transaction_kind="IMPLEMENTATION", + branch="codex/candidate", last_verified_sha=sha, + next_action="publish_first_implementation_pull_request", + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews, + ) + + self.assertTrue(execution_host._has_current_assurance_evidence(state)) + self.assertTrue(execution_host._has_current_local_validation_evidence(state, context)) + self.assertTrue(execution_host._required_validation_controls_pass(state, context)) + self.assertEqual(execution_host._managed_prompt_phase(state, context), "FIRST_PUBLICATION") + + self.assertFalse(execution_host._has_current_assurance_evidence(replace(state, assurance_profile=None))) + self.assertFalse(execution_host._has_current_assurance_evidence( + replace(state, assurance_profile={"candidate_sha": sha}), + )) + blocking = {**reviews[0], "findings": [{"blocking": True, "disposition": "OPEN"}]} + self.assertFalse(execution_host._has_current_assurance_evidence( + replace(state, assurance_reviews=(blocking, reviews[1])), + )) + self.assertFalse(execution_host._has_current_local_validation_evidence( + replace(state, last_verified_sha="d" * 40), context, + )) + self.assertFalse(execution_host._has_current_local_validation_evidence( + replace(state, local_validation_audit=()), context, + )) + self.assertFalse(execution_host._has_current_local_validation_evidence( + replace(state, assurance_profile={**profile, "validation_profile_digest": "sha256:" + "e" * 64}), context, + )) + self.assertFalse(execution_host._required_validation_controls_pass(state, None)) + self.assertFalse(execution_host._required_validation_controls_pass( + state, {**context, "controls": {"repository_suite": {"result": "PASS"}}}, + )) + self.assertIsNone(execution_host._validation_profile_digest( + {**context, "profile_reference": None}, + )) + + self.assertEqual(execution_host._managed_prompt_phase(None), "UNSPECIFIED") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, execution_mode="GENESIS")), "GENESIS") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, transaction_kind="RECONCILIATION")), "RECONCILIATION") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, action_intent="VALIDATION_ONLY")), "VALIDATION_ONLY") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, transaction_kind="FINALIZATION")), "FINALIZATION") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, phase="LOCAL_REPOSITORY_VALIDATION")), "LOCAL_VALIDATION") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, phase="QUALITY_CONTROL_AGENT")), "ASSURANCE_REVIEW") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, phase="REPAIR_AGENT", pull_request=None)), "PREPUBLICATION_REPAIR") + self.assertEqual(execution_host._managed_prompt_phase(replace(state, phase="REPAIR_AGENT", pull_request=101)), "EXISTING_PR_REPAIR") + self.assertEqual(execution_host._managed_prompt_phase( + replace(state, next_action="invoke_agent"), context, + ), "INITIAL_IMPLEMENTATION") + self.assertEqual(execution_host._managed_prompt_phase( + replace(state, execution_mode="UNKNOWN", transaction_kind="OTHER"), context, + ), "UNSPECIFIED") + def test_new_run_initializes_and_records_canonical_prompt(self) -> None: quality_evidence = ({"activity": "TEST_COVERAGE", "result": "Added focused regression coverage."},) agent = FakeAgent(AgentResult("COMPLETE", quality_evidence=quality_evidence)) @@ -1832,6 +1972,12 @@ def test_repair_result_requires_the_durable_plan_and_preserves_its_bounded_pr(se self.assertEqual(conflict.repair_audit[-1]["outcome"], "submitted_for_recheck") self.assertEqual(self.store.load(planned.run_id).repair_audit, conflict.repair_audit) + branch_conflict = runner._advance_after_repair_agent_result( + planned.__class__(**{**planned.__dict__, "run_id": "repair-branch-conflict"}), + AgentResult("COMPLETE", branch="codex/other", pull_request=17, diagnostic="changed branch"), + ) + self.assertEqual((branch_conflict.phase, branch_conflict.next_action), ("BLOCKED", "bounded_scope_conflict")) + failed_plan = planned.__class__(**{**planned.__dict__, "run_id": "repair-agent-failed"}) failed = runner._advance_after_repair_agent_result( failed_plan, AgentResult("FAILED", branch=failed_plan.branch, pull_request=17, diagnostic="provider failure"), @@ -2608,22 +2754,456 @@ def test_quality_assurance_does_not_create_or_replace_the_implementation_pr(self def test_first_implementation_publication_is_a_separate_post_assurance_dispatch(self) -> None: """The product gate, not provider wording, owns first PR creation.""" sha = "a" * 40 + branch, pr_number = "codex/publication-gate", 71 + validation_profile_digest = self._record_passing_validation_profile("publication-gate") profile = {"version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, - "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha} + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest} reviews = tuple({"reviewer": role, "status": "PASS", "candidate_sha": sha, "profile_digest": profile["digest"], "invocation_id": f"{role}-1", "findings": [], "contract_version": "1.0", "started_at": "now", "completed_at": "now"} for role in ("quality", "security")) - agent = SequencedFakeAgent([AgentResult("COMPLETE", "main", 71, commit_sha=sha)]) - runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + repository = FakeRepository(clean=True, branch=branch, contains=False) + github = FakeGitHub([PullRequestEvidence( + pr_number, "OPEN", False, False, is_draft=True, + head_branch=branch, base_branch="main", head_sha=sha, + )]) + agent = SequencedFakeAgent([AgentResult("COMPLETE", branch, pr_number, commit_sha=sha)]) + runner = EngineeringRunner(self.root, self.store, repository, github, agent, lambda _: None) state = TransactionState("publication-gate", "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", - branch="main", owner_authorized=True, assurance_profile=profile, assurance_reviews=reviews) + branch=branch, owner_authorized=True, last_verified_sha=sha, + validation_evidence=({"command": "canonical suite", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews) + with patch.object(github, "pull_request", wraps=github.pull_request) as readback: + published, result = runner._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", branch, commit_sha=sha) + ) + readback.assert_called_with(pr_number) + self.assertFalse(published.terminal) + self.assertEqual(result.pull_request, pr_number) + self.assertEqual(github.ready_calls, []) + self.assertEqual(github.merge_calls, []) + self.assertIn("First implementation pull-request publication gate", agent.prompts[0]) + + def test_first_publication_rejects_missing_or_stale_local_validation_despite_reviews(self) -> None: + sha = "a" * 40 + profile = {"version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha} + reviews = tuple({"reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": []} + for role in ("quality", "security")) + for state in ( + TransactionState("missing-validation", "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch="main", owner_authorized=True, last_verified_sha=sha, + assurance_profile=profile, assurance_reviews=reviews), + TransactionState("stale-validation", "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch="main", owner_authorized=True, last_verified_sha="d" * 40, + validation_evidence=({"command": "canonical suite", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews), + ): + with self.subTest(run_id=state.run_id): + agent = FakeAgent(AgentResult("COMPLETE", "main", 71, commit_sha=sha)) + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + blocked, _ = runner._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", "main", commit_sha=sha) + ) + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "implementation_publication_assurance_required") + self.assertEqual(agent.prompts, []) + + def test_first_publication_requires_current_terminal_receipts_not_pass_prose(self) -> None: + sha = "a" * 40 + for run_id, exit_code, currentness, repair_iterations in ( + ("prose-only-validation", None, None, 0), + ("failed-terminal-validation", 1, 0, 0), + ("stale-terminal-validation", 0, 0, 1), + ): + with self.subTest(run_id=run_id): + record_validation_profile( + self.root, run_id=run_id, selected_validation_tier="FULL", + validation_profile_version=execution_host.VALIDATION_PROFILE_VERSION, + required_validation_controls=("repository_suite",), + recorded_at="2026-08-29T00:00:00+00:00", + ) + if currentness is not None: + record_validation_command_invocation( + self.root, run_id=run_id, validation_id="repository_suite", + command_id=f"{run_id}-command", category="repository", + control_identity="python3 -m unittest discover", required_for_profile=True, + started_at="2026-08-29T00:00:00+00:00", currentness=currentness, + ) + record_validation_command_terminal( + self.root, run_id=run_id, command_id=f"{run_id}-command", + completed_at="2026-08-29T00:00:01+00:00", exit_code=exit_code, + ) + context = load_validation_context(self.root, run_id) + assert context is not None + validation_profile_digest = execution_host._validation_profile_digest(context) + assert validation_profile_digest is not None + profile = { + "version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest, + } + reviews = tuple({ + "reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": [], + } for role in ("quality", "security")) + state = TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch="codex/implementation", owner_authorized=True, + last_verified_sha=sha, repair_iterations=repair_iterations, + validation_evidence=({"command": "canonical suite", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews, + ) + agent = FakeAgent(AgentResult( + "COMPLETE", "codex/implementation", 71, commit_sha=sha, + )) + blocked, _ = EngineeringRunner( + self.root, self.store, + FakeRepository(branch="codex/implementation"), FakeGitHub([]), agent, + lambda _: None, + )._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", "codex/implementation", commit_sha=sha), + ) + self.assertTrue(blocked.terminal) + self.assertEqual( + blocked.next_action, "implementation_publication_assurance_required", + ) + self.assertEqual(agent.prompts, []) + + def test_recovered_publication_result_is_reconciled_without_a_second_provider_turn(self) -> None: + sha, branch, pr_number = "a" * 40, "codex/recovered-publication", 71 + validation_profile_digest = self._record_passing_validation_profile("recovered-publication") + profile = {"version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest} + reviews = tuple({"reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": []} + for role in ("quality", "security")) + github = FakeGitHub([PullRequestEvidence( + pr_number, "OPEN", False, False, is_draft=True, + head_branch=branch, base_branch="main", head_sha=sha, + )]) + agent = FakeAgent(AgentResult("BLOCKED", diagnostic="must not be invoked")) + runner = EngineeringRunner( + self.root, self.store, FakeRepository(clean=True, branch=branch, contains=False), github, agent, lambda _: None, + ) + state = TransactionState( + "recovered-publication", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + branch=branch, owner_authorized=True, next_action="publish_first_implementation_pull_request", + last_verified_sha=sha, validation_evidence=({"command": "canonical suite", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), assurance_profile=profile, assurance_reviews=reviews, + ) published, result = runner._publish_first_implementation_pull_request( - state, AgentResult("COMPLETE", "main", commit_sha=sha) + state, AgentResult("COMPLETE", branch, pr_number, commit_sha=sha), ) self.assertFalse(published.terminal) - self.assertEqual(result.pull_request, 71) - self.assertIn("First implementation pull-request publication gate", agent.prompts[0]) + self.assertEqual(result.pull_request, pr_number) + self.assertEqual(agent.prompts, []) + + def test_managed_host_orders_first_pr_after_validation_and_both_reviews(self) -> None: + """Exercise the real transitions; the adapter observes PR creation only at publication.""" + events: list[str] = [] + + class OrderedAgent(SequencedFakeAgent): + command_callback: object | None = None + + def set_command_callback(self, callback: object) -> None: + self.command_callback = callback + + def invoke(self, root: Path, prompt: str) -> AgentResult: + if "First implementation publication hand-off boundary" in prompt: + events.append("PUBLICATION_DISPATCH") + elif "Local repository validation hand-off boundary" in prompt: + events.append("LOCAL_VALIDATION_PASS") + else: + events.append("IMPLEMENTATION") + result = super().invoke(root, prompt) + if events[-1] == "IMPLEMENTATION": + self.repository.evidence = RepositoryEvidence( + "pcvantol/djconnect", result.branch or "main", "a" * 40, True, True, + ) + elif events[-1] == "LOCAL_VALIDATION_PASS" and callable(self.command_callback): + for ordinal, command in enumerate(("git diff --check", "python -m unittest discover"), start=1): + command_id = f"ordered-validation-{ordinal}" + self.command_callback("started", command_id, command) + self.command_callback("completed", command_id, command, 0) + if result.pull_request: + events.append("PR_CREATE") + return result + + def review(self, root: Path, selection: object, objective: str, evidence: object = None) -> ReviewerResult: + events.append(f"{getattr(selection, 'reviewer').upper()}_REVIEW_PASS") + return super().review(root, selection, objective, evidence) + + agent = OrderedAgent([ + AgentResult("COMPLETE", "codex/ordered-publication", commit_sha="a" * 40), + AgentResult("COMPLETE", "codex/ordered-publication", commit_sha="a" * 40, + validation_evidence=({"command": "canonical suite", "result": "passed"},)), + AgentResult("COMPLETE", "codex/ordered-publication", 71, commit_sha="a" * 40), + ]) + repository = FakeRepository() + agent.repository = repository + github = FakeGitHub([PullRequestEvidence(71, "OPEN", True, True, is_draft=True, head_branch="codex/ordered-publication", base_branch="main", head_sha="a" * 40)]) + state = EngineeringRunner(self.root, self.store, repository, github, agent, lambda _: None).run( + self.prompt, run_id="ordered-first-publication", owner_authorized=True, + ) + lifecycle_events = [event for event in events if event != "DOCUMENTATION_REVIEW_PASS"] + self.assertFalse( + state.terminal, + f"{state.diagnostic}; {load_validation_context(self.root, state.run_id)}; {state.assurance_profile}", + ) + self.assertEqual(lifecycle_events, [ + "IMPLEMENTATION", "LOCAL_VALIDATION_PASS", "QUALITY_REVIEW_PASS", + "SECURITY_REVIEW_PASS", "PUBLICATION_DISPATCH", "PR_CREATE", + ]) + self.assertEqual(state.pull_request, 71) + self.assertEqual(lifecycle_events[:4].count("PR_CREATE"), 0) + + def test_adopted_candidate_runs_real_host_gates_without_reimplementation(self) -> None: + sha, branch = "a" * 40, "codex/adopted-publication" + events: list[str] = [] + + class AdoptionAgent(SequencedFakeAgent): + command_callback: object | None = None + + def set_command_callback(self, callback: object) -> None: + self.command_callback = callback + + def invoke(self, root: Path, prompt: str) -> AgentResult: + if "Local repository validation hand-off boundary" in prompt: + events.append("LOCAL_VALIDATION_PASS") + elif "First implementation publication hand-off boundary" in prompt: + events.append("PUBLICATION_DISPATCH") + else: + events.append("UNEXPECTED_IMPLEMENTATION") + result = super().invoke(root, prompt) + if events[-1] == "LOCAL_VALIDATION_PASS" and callable(self.command_callback): + for ordinal, command in enumerate(("git diff --check", "python -m unittest discover"), start=1): + command_id = f"adoption-validation-{ordinal}" + self.command_callback("started", command_id, command) + self.command_callback("completed", command_id, command, 0) + if result.pull_request: + events.append("PR_CREATE") + return result + + def review(self, root: Path, selection: object, objective: str, evidence: object = None) -> ReviewerResult: + events.append(f"{getattr(selection, 'reviewer').upper()}_REVIEW_PASS") + return super().review(root, selection, objective, evidence) + + agent = AdoptionAgent([ + AgentResult( + "COMPLETE", branch, commit_sha=sha, + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), + ), + AgentResult("COMPLETE", branch, 71, commit_sha=sha), + ]) + repository = FakeRepository(branch=branch) + github = FakeGitHub([PullRequestEvidence( + 71, "OPEN", True, True, is_draft=True, + head_branch=branch, base_branch="main", head_sha=sha, + )]) + state = EngineeringRunner( + self.root, self.store, repository, github, agent, lambda _: None, + ).run_adopted_candidate( + self.prompt, run_id="adopted-publication", resume=False, + branch=branch, candidate_sha=sha, + ) + lifecycle_events = [event for event in events if event != "DOCUMENTATION_REVIEW_PASS"] + self.assertEqual(lifecycle_events, [ + "LOCAL_VALIDATION_PASS", "QUALITY_REVIEW_PASS", "SECURITY_REVIEW_PASS", + "PUBLICATION_DISPATCH", "PR_CREATE", + ]) + self.assertNotIn("UNEXPECTED_IMPLEMENTATION", events) + self.assertEqual(state.pull_request, 71) + self.assertEqual(repository.synchronize_calls, []) + + def test_publication_checkpoint_resume_reconciles_exact_pr_without_replaying_provider(self) -> None: + run_id, sha, branch = "publication-checkpoint-resume", "a" * 40, "codex/publication-resume" + validation_profile_digest = self._record_passing_validation_profile(run_id) + profile = { + "version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest, + } + reviews = tuple({ + "reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "invocation_id": f"{role}-resume", + "findings": [], "contract_version": "1.0", + "started_at": "now", "completed_at": "now", + } for role in ("quality", "security")) + self.store.save(TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + branch=branch, owner_authorized=True, + next_action="publish_first_implementation_pull_request", + last_verified_sha=sha, + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), + local_validation_iterations=1, + local_validation_audit=({ + "iteration": "1", "observed_at": "2026-08-29T00:00:01+00:00", + "failed_checks": "No failed controls.", "proposed_action": "FULL profile.", + "agent_summary": "Required controls passed.", "commit_sha": sha, + "outcome": "validated", + },), + assurance_profile=profile, assurance_reviews=reviews, + )) + pull_request = PullRequestEvidence( + 71, "OPEN", True, True, is_draft=True, + head_branch=branch, base_branch="main", head_sha=sha, + ) + github = FakeGitHub([pull_request], branch_response=pull_request) + repository = FakeRepository(branch=branch) + agent = FakeAgent(AgentResult("BLOCKED", diagnostic="must not be invoked")) + state = EngineeringRunner( + self.root, self.store, repository, github, agent, lambda _: None, + ).run(self.prompt, run_id=run_id, resume=True) + self.assertEqual(state.phase, "WAIT_FOR_OPERATOR_MERGE") + self.assertEqual(state.pull_request, 71) + self.assertEqual(agent.prompts, []) + self.assertEqual(repository.synchronize_calls, []) + self.assertEqual(github.branch_calls, [branch]) + + def test_publication_interruption_never_enters_generic_provider_retry(self) -> None: + state = TransactionState( + "publication-interruption", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + branch="codex/publication-interruption", owner_authorized=True, + next_action="publish_first_implementation_pull_request", + ) + runner = EngineeringRunner( + self.root, self.store, FakeRepository(), FakeGitHub([]), + FakeAgent(AgentResult("COMPLETE")), lambda _: None, + ) + recovery = { + "state": "RECOVERY_AVAILABLE", "lifecycle_phase": "EXECUTE_AGENT", + "triggering_invocation_id": "publication-attempt-1", + } + with patch.object(runner, "_recovery_state", return_value=recovery), patch.object( + runner, "_invoke_provider_attempt_with_timing", + ) as dispatched: + with self.assertRaises(CodexInvocationError) as raised: + runner._invoke_agent_with_timing(state, "publication prompt") + self.assertTrue(raised.exception.provider_turn_interrupted) + self.assertEqual(raised.exception.terminal_condition, "provider_turn_interrupted") + dispatched.assert_not_called() + + def test_publication_readback_mismatch_blocks_before_create_dispatch(self) -> None: + run_id, sha, branch = "publication-readback-mismatch", "a" * 40, "codex/publication-mismatch" + validation_profile_digest = self._record_passing_validation_profile(run_id) + profile = { + "version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest, + } + reviews = tuple({ + "reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": [], + } for role in ("quality", "security")) + state = TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch=branch, owner_authorized=True, last_verified_sha=sha, + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews, + ) + mismatching = PullRequestEvidence( + 71, "OPEN", True, True, is_draft=True, + head_branch=branch, base_branch="main", head_sha="d" * 40, + ) + agent = FakeAgent(AgentResult("COMPLETE", branch, 72, commit_sha=sha)) + blocked, _ = EngineeringRunner( + self.root, self.store, FakeRepository(branch=branch), + FakeGitHub([], branch_response=mismatching), agent, lambda _: None, + )._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", branch, commit_sha=sha), + ) + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "implementation_publication_evidence_invalid") + self.assertEqual(agent.prompts, []) + + def test_publication_readback_error_blocks_before_create_dispatch(self) -> None: + run_id, sha, branch = "publication-readback-error", "a" * 40, "codex/publication-readback-error" + validation_profile_digest = self._record_passing_validation_profile(run_id) + profile = { + "version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest, + } + reviews = tuple({ + "reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": [], + } for role in ("quality", "security")) + state = TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch=branch, owner_authorized=True, last_verified_sha=sha, + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews, + ) + agent = FakeAgent(AgentResult("COMPLETE", branch, 72, commit_sha=sha)) + blocked, _ = EngineeringRunner( + self.root, self.store, FakeRepository(branch=branch), + FakeGitHub([], branch_response=RunnerError("GitHub readback unavailable")), agent, lambda _: None, + )._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", branch, commit_sha=sha), + ) + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "implementation_publication_evidence_invalid") + self.assertIn("not dispatched", blocked.diagnostic) + self.assertEqual(agent.prompts, []) + + def test_publication_acknowledgement_readback_error_blocks_without_retry(self) -> None: + run_id, sha, branch = "publication-ack-readback-error", "a" * 40, "codex/publication-ack-error" + validation_profile_digest = self._record_passing_validation_profile(run_id) + profile = { + "version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest, + } + reviews = tuple({ + "reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": [], + } for role in ("quality", "security")) + state = TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch=branch, owner_authorized=True, last_verified_sha=sha, + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews, + ) + + class InterruptedPublicationAgent(FakeAgent): + def invoke(self, root: Path, prompt: str) -> AgentResult: + self.roots.append(root) + self.prompts.append(prompt) + raise CodexInvocationError( + "Provider acknowledgement was interrupted.", "redacted provider detail", + next_action="NONE", terminal_condition="provider_turn_interrupted", + interruption_reason="interrupted", + ) + + class UncertainReadbackGitHub(FakeGitHub): + def pull_request_for_head_branch(self, requested: str) -> PullRequestEvidence | None: + self.branch_calls.append(requested) + if len(self.branch_calls) == 1: + return None + raise RunnerError("GitHub acknowledgement readback unavailable") + + agent = InterruptedPublicationAgent(AgentResult("BLOCKED")) + github = UncertainReadbackGitHub([]) + blocked, _ = EngineeringRunner( + self.root, self.store, FakeRepository(branch=branch), github, agent, lambda _: None, + )._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", branch, commit_sha=sha), + ) + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "implementation_publication_evidence_invalid") + self.assertIn("reconciled before any retry", blocked.diagnostic) + self.assertEqual(len(agent.prompts), 1) + self.assertEqual(github.branch_calls, [branch, branch]) def test_malformed_mandatory_review_is_unresolved_not_an_empty_pass(self) -> None: class MalformedReviewer(FakeAgent): @@ -2640,10 +3220,25 @@ def test_reserved_repair_revalidates_and_rereviews_before_returning_to_pr_eviden """One repair consumes one durable round and cannot bypass assurance.""" sha = "a" * 40 branch = "codex/repair-rereview" - agent = SequencedFakeAgent([ + class RepairValidationAgent(SequencedFakeAgent): + command_callback: object | None = None + + def set_command_callback(self, callback: object) -> None: + self.command_callback = callback + + def invoke(self, root: Path, prompt: str) -> AgentResult: + result = super().invoke(root, prompt) + if "Local repository validation hand-off boundary" in prompt and callable(self.command_callback): + for ordinal, command in enumerate(("git diff --check", "python -m unittest discover"), start=1): + command_id = f"repair-validation-{ordinal}" + self.command_callback("started", command_id, command) + self.command_callback("completed", command_id, command, 0) + return result + + agent = RepairValidationAgent([ AgentResult("COMPLETE", branch, commit_sha=sha), AgentResult("COMPLETE", branch, commit_sha=sha, - validation_evidence=({"command": "canonical suite", "result": "passed"},)), + validation_evidence=({"command": "python -m unittest discover", "result": "passed"},)), ]) github = FakeGitHub([PullRequestEvidence(71, "OPEN", True, True, head_branch=branch, base_branch="main")]) runner = EngineeringRunner(self.root, self.store, FakeRepository(branch=branch), github, agent, lambda _: None) @@ -3260,6 +3855,8 @@ def test_genesis_mode_reconciles_a_clean_local_commit_without_remote_or_pr(self) self.assertIsNone(state.pull_request) self.assertEqual([review["reviewer"] for review in state.assurance_reviews], ["quality", "security"]) self.assertEqual([review["status"] for review in state.assurance_reviews], ["PASS", "PASS"]) + self.assertNotIn("validation_profile_digest", state.assurance_profile) + self.assertEqual(TransactionState.from_dict(state.to_dict()), state) def test_genesis_selects_its_target_before_managed_cleanliness_checks(self) -> None: target = self.root.parent / f"genesis-clean-{self.root.name}" @@ -4848,8 +5445,65 @@ def test_managed_repair_prompt_returns_same_pr_to_host(self) -> None: transaction_kind="FINALIZATION", owner_authorized=True, repair_iterations=1, ) prompt = assemble_prompt(self.prompt, state, managed_target=self.root) - self.assertIn("preserve the exact checkpointed pull-request number", prompt) - self.assertIn("at most three bounded repairs", prompt) + self.assertIn("PR hand-off boundary", prompt) + self.assertIn("preserving any checkpointed pull-request number", prompt) + + def test_managed_prompt_handoffs_are_phase_consistent_and_publication_is_explicit(self) -> None: + """The full composer, not an isolated suffix, selects one PR contract.""" + sha = "a" * 40 + validation_profile_digest = self._record_passing_validation_profile("publication") + profile = {"version": "validation-profile@1.0", "digest": "sha256:" + "b" * 64, + "criteria_digest": "sha256:" + "c" * 64, "candidate_sha": sha, + "validation_profile_digest": validation_profile_digest} + reviews = tuple({"reviewer": role, "status": "PASS", "candidate_sha": sha, + "profile_digest": profile["digest"], "findings": []} + for role in ("quality", "security")) + cases = ( + (TransactionState("initial", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", owner_authorized=True), + "Implementation hand-off boundary", "Do not create a draft or normal implementation pull request"), + (TransactionState("validation", "pcvantol/djconnect", str(self.prompt), "LOCAL_REPOSITORY_VALIDATION", owner_authorized=True), + "Local repository validation hand-off boundary", "A pull request is neither required nor permitted"), + (TransactionState("review", "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", owner_authorized=True), + "Mandatory assurance hand-off boundary", "Do not edit, commit, push, create a pull request"), + (TransactionState("repair", "pcvantol/djconnect", str(self.prompt), "REPAIR_AGENT", branch="codex/repair", owner_authorized=True), + "Pre-publication repair hand-off boundary", "No pull request exists yet; do not require, create, or invent one"), + (TransactionState("publication", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", branch="codex/implementation", owner_authorized=True, + next_action="publish_first_implementation_pull_request", last_verified_sha=sha, + validation_evidence=({"command": "canonical suite", "result": "passed"},), + local_validation_audit=({"outcome": "validated"},), + assurance_profile=profile, assurance_reviews=reviews), + "First implementation publication hand-off boundary", "Create exactly one draft implementation pull request"), + (TransactionState("existing-repair", "pcvantol/djconnect", str(self.prompt), "REPAIR_AGENT", branch="codex/repair", pull_request=701, owner_authorized=True), + "Existing implementation-PR repair hand-off boundary", "Do not create a replacement or second pull request"), + ) + for state, heading, expected in cases: + with self.subTest(run_id=state.run_id): + validation_context = ( + load_validation_context(self.root, state.run_id) + if state.run_id == "publication" else None + ) + prompt = assemble_prompt( + self.prompt, state, managed_target=self.root, + validation_context=validation_context, + ) + self.assertIn(heading, prompt) + self.assertIn(expected, prompt) + publication_prompt = assemble_prompt( + self.prompt, cases[4][0], managed_target=self.root, + validation_context=load_validation_context(self.root, "publication"), + ) + self.assertNotIn("Do not create a draft or normal implementation pull request", publication_prompt) + + def test_publication_action_without_host_assurance_fails_closed_in_full_prompt(self) -> None: + state = TransactionState( + "untrusted-publication", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + branch="codex/implementation", owner_authorized=True, + next_action="publish_first_implementation_pull_request", + ) + prompt = assemble_prompt(self.prompt, state, managed_target=self.root) + self.assertIn("Implementation hand-off boundary", prompt) + self.assertIn("Do not create a draft or normal implementation pull request", prompt) + self.assertNotIn("Create exactly one draft implementation pull request", prompt) def test_primary_investigation_ledger_reuses_only_current_facts(self) -> None: ledger = InvocationInvestigationLedger().record( diff --git a/tests/engineering/test_parity_lifecycle_dispatcher.py b/tests/engineering/test_parity_lifecycle_dispatcher.py index 55d5e822..3ec2f94b 100644 --- a/tests/engineering/test_parity_lifecycle_dispatcher.py +++ b/tests/engineering/test_parity_lifecycle_dispatcher.py @@ -62,6 +62,19 @@ def run(self, prompt_path: Path, run_id: str | None = None, resume: bool = False return TransactionState(run_id or "inbox-missing", "fixture", str(prompt_path), "FAILED", terminal=True) +class _AdoptionRunner: + calls: list[tuple[Path, str, bool, str, str]] = [] + + def run(self, prompt_path: Path, run_id: str | None = None, resume: bool = False, + owner_authorized: bool = False, transaction_kind: str = "IMPLEMENTATION") -> TransactionState: + raise AssertionError("Typed candidate adoption must not dispatch ordinary implementation") + + def run_adopted_candidate(self, prompt_path: Path, *, run_id: str, resume: bool, + branch: str, candidate_sha: str) -> TransactionState: + self.calls.append((prompt_path, run_id, resume, branch, candidate_sha)) + return TransactionState(run_id, "fixture", str(prompt_path), "COMPLETE", terminal=True) + + class _CheckpointingRunner: """A deterministic preserved-runner seam for CENTRAL storage qualification.""" @@ -98,6 +111,7 @@ def setUp(self) -> None: connection.execute("INSERT INTO ep_repository_registrations VALUES(?,?,?,?,?,?,?)", (project, project, project, "authority", "{}", now, now)) local_repository_binding.bind_local_repository(connection, project_id=project, repository_id=project, local_root=root, data_root=self.data) _Runner.calls = [] + _AdoptionRunner.calls = [] def tearDown(self) -> None: self.temporary.cleanup() @@ -119,12 +133,54 @@ def test_dispatcher_import_does_not_load_retired_inbox_watcher_runtime(self) -> ) self.assertEqual(json.loads(completed.stdout), False) - def _submission(self, project: str, prompt: str = "Validate only.") -> str: + def _submission(self, project: str, prompt: str = "Validate only.", constraints: dict[str, object] | None = None) -> str: with sqlite3.connect(self.data / server.SERVER_DATABASE_FILENAME) as connection: return submission_service.submit(connection, submission_service.SubmissionRequest( - project, project, "canary", "HUMAN", "1", prompt, "HTTP", + project, project, "canary", "HUMAN", "1", prompt, "HTTP", constraints=constraints, )).submission_id + def test_candidate_adoption_is_typed_and_requires_explicit_authority(self) -> None: + submission = self._submission( + "alpha", "Execution Mode: Managed\n", + {"managed_candidate_adoption": { + "branch": "codex/adopt-existing-candidate", + "candidate_sha": "a" * 40, + "owner_authorized": True, + }}, + ) + with sqlite3.connect(self.data / server.SERVER_DATABASE_FILENAME) as connection: + context = parity_lifecycle_dispatcher.project_context( + connection, data_root=self.data, project_id="alpha", repository_id="alpha", + ) + candidate = parity_lifecycle_dispatcher.historical_candidate( + connection, context=context, submission_id=submission, + ) + self.assertEqual(candidate.candidate_adoption(), ("codex/adopt-existing-candidate", "a" * 40)) + + def test_dispatcher_sends_typed_candidate_to_the_host_adoption_entrypoint(self) -> None: + branch, candidate_sha = "codex/adopt-existing-candidate", "a" * 40 + submission = self._submission( + "alpha", "Execution Mode: Managed\n", + {"managed_candidate_adoption": { + "branch": branch, "candidate_sha": candidate_sha, + "owner_authorized": True, + }}, + ) + dispatcher = ParityLifecycleDispatcher( + self.data, runner_factory=lambda root: _AdoptionRunner(), + ) + with patch("engineering_platform.parity_lifecycle_dispatcher.execute_host_preflight", return_value=_PassingPreflight()), \ + patch("engineering_platform.parity_lifecycle_dispatcher.execute_workspace_preflight", return_value=_PassingPreflight()), \ + patch("engineering_platform.parity_lifecycle_dispatcher.execute_capability_preflight", return_value=_PassingPreflight()): + receipt = dispatcher.dispatch(submission) + self.assertEqual(receipt.state, "COMPLETE") + self.assertEqual(len(_AdoptionRunner.calls), 1) + prompt_path, run_id, resume, dispatched_branch, dispatched_sha = _AdoptionRunner.calls[0] + self.assertEqual(run_id, receipt.run_id) + self.assertFalse(resume) + self.assertEqual((dispatched_branch, dispatched_sha), (branch, candidate_sha)) + self.assertTrue(prompt_path.is_file()) + def test_claims_one_submission_once_and_preserves_central_run_linkage(self) -> None: submission = self._submission("alpha") dispatcher = ParityLifecycleDispatcher(self.data, runner_factory=lambda root: _Runner()) diff --git a/tests/engineering/test_qualification_runtime.py b/tests/engineering/test_qualification_runtime.py index f72fce83..08764454 100644 --- a/tests/engineering/test_qualification_runtime.py +++ b/tests/engineering/test_qualification_runtime.py @@ -112,9 +112,16 @@ def test_external_handoff_writes_only_the_explicit_fixture_contract(self) -> Non def test_local_github_adapter_models_open_then_merged_evidence(self) -> None: adapter = LocalQualificationGitHub(self.root) opened = adapter.pull_request(7) + pre_ready = adapter.pull_request(7) merged = adapter.pull_request(7) self.assertEqual((opened.state, opened.head_branch), ("OPEN", "qualification-managed")) + self.assertTrue(opened.is_draft) + self.assertEqual(opened.head_sha, subprocess.run( + ("git", "-C", str(self.root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True, + ).stdout.strip()) + self.assertEqual(pre_ready.state, "OPEN") + self.assertTrue(pre_ready.is_draft) self.assertEqual((merged.state, merged.head_branch), ("MERGED", "qualification-managed")) self.assertTrue(merged.merge_commit) self.assertIsNone(adapter.pull_request_for_head_branch("ignored")) diff --git a/tools/qualification/build_platform_wheel.py b/tools/qualification/build_platform_wheel.py index f8ef7b27..5b6e603a 100644 --- a/tools/qualification/build_platform_wheel.py +++ b/tools/qualification/build_platform_wheel.py @@ -4,13 +4,33 @@ from __future__ import annotations import argparse +import io from pathlib import Path import subprocess import sys +import tarfile +import tempfile from advance_platform_build import _current_version +def _extract_committed_source(root: Path, destination: Path) -> None: + """Materialize exactly HEAD without consuming ignored build residue.""" + tracked = subprocess.run( + ("git", "-C", str(root), "status", "--porcelain", "--untracked-files=no"), + check=True, text=True, capture_output=True, + ) + if tracked.stdout.strip(): + raise RuntimeError("candidate source has uncommitted tracked changes") + archive = subprocess.run( + ("git", "-C", str(root), "archive", "--format=tar", "HEAD"), + check=True, capture_output=True, + ).stdout + destination.mkdir(parents=True) + with tarfile.open(fileobj=io.BytesIO(archive), mode="r:") as source: + source.extractall(destination, filter="data") + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Build an already-versioned EP wheel without source mutation") parser.add_argument("--source-root", type=Path, default=Path.cwd()) @@ -25,12 +45,21 @@ def main(argv: list[str] | None = None) -> int: raise RuntimeError(f"requested build version {args.version} does not match committed source {version}; prepare it first") wheel_directory = (args.wheel_directory or root / "dist").resolve() wheel_directory.mkdir(parents=True, exist_ok=True) - if args.sdist: - # Do not import an incidental ``build/`` directory from the source - # checkout in preference to the PyPA build module. - subprocess.run((sys.executable, "-m", "build", "--outdir", str(wheel_directory), str(root)), check=True, cwd=root.parent) - else: - subprocess.run((sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(wheel_directory), str(root)), check=True) + with tempfile.TemporaryDirectory(prefix="ep-committed-wheel-source-") as temporary: + staged_root = Path(temporary) / "source" + _extract_committed_source(root, staged_root) + if _current_version(staged_root) != version: + raise RuntimeError("committed candidate version does not match source version") + if args.sdist: + subprocess.run( + (sys.executable, "-m", "build", "--outdir", str(wheel_directory), str(staged_root)), + check=True, cwd=Path(temporary), + ) + else: + subprocess.run( + (sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(wheel_directory), str(staged_root)), + check=True, cwd=Path(temporary), + ) wheel = wheel_directory / f"engineering_platform-{version}-py3-none-any.whl" if not wheel.is_file(): raise RuntimeError("expected canonical Engineering Platform wheel was not produced") diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 7b794d19..2200f558 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -80,7 +80,15 @@ def create_repository(path: Path, *, origin: Path | None = None) -> None: git(path, "config", "user.email", "qualification@example.invalid") git(path, "config", "user.name", "Installed qualification") (path / "BOOTSTRAP.md").write_text("# Installed qualification\n", encoding="utf-8") - git(path, "add", "BOOTSTRAP.md") + (path / "test_installed_platform.py").write_text( + "import unittest\n\n" + "import engineering_platform\n\n\n" + "class InstalledPlatformTest(unittest.TestCase):\n" + " def test_package_is_importable(self):\n" + " self.assertIsNotNone(engineering_platform.__file__)\n", + encoding="utf-8", + ) + git(path, "add", "BOOTSTRAP.md", "test_installed_platform.py") git(path, "commit", "-qm", "initial qualification repository") if origin is not None: subprocess.run(("git", "init", "-q", "--bare", str(origin)), check=True) # nosec B603 @@ -299,7 +307,15 @@ def main(argv: list[str] | None = None) -> int: root, wheelhouse, venv, data = Path(temporary), Path(temporary) / "wheelhouse", Path(temporary) / "venv", Path(temporary) / "central" root.mkdir(mode=0o700, parents=True, exist_ok=True) wheelhouse.mkdir() - subprocess.run((sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(wheelhouse), str(args.source_root)), check=True, capture_output=True, text=True) # nosec B603 + subprocess.run( + ( + sys.executable, + str(args.source_root.resolve() / "tools" / "qualification" / "build_platform_wheel.py"), + "--source-root", str(args.source_root.resolve()), + "--wheel-directory", str(wheelhouse), + ), + check=True, capture_output=True, text=True, + ) # nosec B603 subprocess.run((sys.executable, "-m", "venv", str(venv)), check=True) # nosec B603 wheels = tuple(wheelhouse.glob("engineering_platform-*.whl")) if len(wheels) != 1: diff --git a/tools/qualification/p_transport_installed_ingress_matrix.py b/tools/qualification/p_transport_installed_ingress_matrix.py index db4aafa2..a30a28b7 100644 --- a/tools/qualification/p_transport_installed_ingress_matrix.py +++ b/tools/qualification/p_transport_installed_ingress_matrix.py @@ -250,7 +250,15 @@ def main(argv: list[str] | None = None) -> int: # Build the candidate exactly as an installer would. The qualification # runner must not assume that its own interpreter happens to carry the # project build backend; build isolation is part of the wheel contract. - subprocess.run([sys.executable, "-m", "pip", "wheel", "--no-deps", "--wheel-dir", str(wheelhouse), str(args.source_root)], check=True, capture_output=True, text=True) # nosec B603 + subprocess.run( + [ + sys.executable, + str(args.source_root.resolve() / "tools" / "qualification" / "build_platform_wheel.py"), + "--source-root", str(args.source_root.resolve()), + "--wheel-directory", str(wheelhouse), + ], + check=True, capture_output=True, text=True, + ) # nosec B603 wheels = tuple(wheelhouse.glob("engineering_platform-*.whl")) if len(wheels) != 1: raise RuntimeError("CANDIDATE_WHEEL_UNAVAILABLE")