From 2ce2d556b454eabf3389afda34e8ef2c2ca19c43 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 21:07:39 +0200 Subject: [PATCH 01/87] feat: add run quality assurance --- .../ENGINEERING_PLATFORM_ROADMAP.md | 7 + .../EXECUTION_HOST_ARCHITECTURE.md | 15 +- src/engineering_platform/agent_state.py | 44 +++++ src/engineering_platform/assets/dashboard.js | 26 +++ .../assets/dashboard_locales.mjs | 5 + src/engineering_platform/capability_review.py | 2 + src/engineering_platform/execution_host.py | 167 ++++++++++-------- .../execution_lifecycle.py | 8 + src/engineering_platform/server.py | 2 +- .../submission_service.py | 35 +++- tests/engineering/test_execution_host.py | 32 ++-- tests/engineering/test_execution_lifecycle.py | 16 ++ 12 files changed, 267 insertions(+), 92 deletions(-) diff --git a/docs/development/ENGINEERING_PLATFORM_ROADMAP.md b/docs/development/ENGINEERING_PLATFORM_ROADMAP.md index 66cd37e4..699933d8 100644 --- a/docs/development/ENGINEERING_PLATFORM_ROADMAP.md +++ b/docs/development/ENGINEERING_PLATFORM_ROADMAP.md @@ -243,6 +243,13 @@ one bounded Forge Action still enter through HTTP, receive durable identity, be admitted, mutate its repository, validate/review/repair/finalize, retain terminal evidence, and reconcile after Forge restart? +`EP_RUN_QUALITY_ASSURANCE_V1` is a bounded EP execution-contract increment: +reuse the existing quality lifecycle step for pinned, independent read-only +quality and security assurance, structured findings/readback and one shared +three-round repair budget. It is not a second orchestrator, generalized Agent +topology, or a claim of installed qualification until exact-head delivery and +installed evidence are retained. + | Umbrella gate | Exact first-loop capability | Evidence state at this proposal | First-loop disposition | | --- | --- | --- | --- | | Phase-3 package/install | A clean installed Server/CENTRAL/runtime that can run the canary | Historical dependency authority says incomplete; no current installed-proof claim is made here | `AUTONOMY_CRITICAL` bounded capability; full historical phase needs reconciliation | diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index 2a8d646b..02f1cbfd 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -28,10 +28,17 @@ Genesis target. A Genesis run only evaluates its target profile; a Managed run only evaluates its repository profile. JSON status files are projections, not an ownership or lifecycle authority. -Lifecycle phase identifiers are compatibility contracts. Their presentation is -mode-aware: the shared `REPAIR_AGENT` phase is projected as pull-request check -repair for Managed work and autonomous quality repair for Genesis. This is a -display-only distinction; no checkpoint or transaction state is translated. +Lifecycle phase identifiers are compatibility contracts. `QUALITY_CONTROL_AGENT` +is the shared post-implementation assurance boundary: it invokes independent +read-only Quality and Security reviewers on one pinned candidate. It cannot +write the repository, create a PR, or approve an implementer's work. Findings +are immutable, versioned checkpoint evidence; a missing, malformed, or +candidate-mismatched required review is `UNRESOLVED`, never a pass. Only the +shared `REPAIR_AGENT` role may correct accepted blockers. Its run-wide, +persistent budget is three rounds total, spanning local validation, assurance, +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. The immutable profile lists repository, remote, upstream, clean-worktree, branch, workspace authorization, host and capability qualification, providers, diff --git a/src/engineering_platform/agent_state.py b/src/engineering_platform/agent_state.py index 6541f531..bab391cc 100644 --- a/src/engineering_platform/agent_state.py +++ b/src/engineering_platform/agent_state.py @@ -135,6 +135,11 @@ class TransactionState: agent_execution_seconds: float | None = None validation_evidence: tuple[dict[str, str], ...] = () quality_evidence: tuple[dict[str, str], ...] = () + # Versioned post-implementation assurance. These records are immutable + # observations; a later review appends a new record instead of changing an + # earlier finding into a pass. + assurance_profile: dict[str, str] | None = None + assurance_reviews: tuple[dict[str, object], ...] = () repair_iterations: int = 0 repair_audit: tuple[dict[str, str], ...] = () local_validation_iterations: int = 0 @@ -172,6 +177,8 @@ def from_dict(cls, raw: object) -> "TransactionState": "agent_execution_seconds": None, "validation_evidence": (), "quality_evidence": (), + "assurance_profile": None, + "assurance_reviews": (), "repair_iterations": 0, "repair_audit": (), "local_validation_iterations": 0, @@ -194,6 +201,8 @@ def from_dict(cls, raw: object) -> "TransactionState": raw = {**raw, "validation_evidence": tuple(raw["validation_evidence"])} if isinstance(raw.get("quality_evidence"), list): raw = {**raw, "quality_evidence": tuple(raw["quality_evidence"])} + if isinstance(raw.get("assurance_reviews"), list): + raw = {**raw, "assurance_reviews": tuple(raw["assurance_reviews"])} if isinstance(raw.get("repair_audit"), list): raw = {**raw, "repair_audit": tuple(raw["repair_audit"])} if isinstance(raw.get("local_validation_audit"), list): @@ -357,6 +366,41 @@ def from_dict(cls, raw: object) -> "TransactionState": ) ): raise StateError("checkpoint quality evidence is invalid or unsafe") + profile_fields = {"version", "digest", "candidate_sha"} + if state.assurance_profile is not None and ( + not isinstance(state.assurance_profile, dict) + or set(state.assurance_profile) != 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"]) + ): + raise StateError("checkpoint assurance profile is invalid") + review_fields = {"reviewer", "status", "candidate_sha", "profile_digest", "invocation_id", "findings"} + finding_fields = {"id", "fingerprint", "category", "criterion", "observation", "severity", "confidence", "blocking", "disposition"} + if ( + not isinstance(state.assurance_reviews, tuple) + or len(state.assurance_reviews) > 16 + or any( + not isinstance(review, dict) or set(review) != review_fields + or review.get("reviewer") not in {"quality", "security"} + or review.get("status") not in {"PASS", "FAIL", "UNRESOLVED"} + or not isinstance(review.get("candidate_sha"), str) or not re.fullmatch(r"[0-9a-f]{40}", review["candidate_sha"]) + or not isinstance(review.get("profile_digest"), str) or not re.fullmatch(r"sha256:[0-9a-f]{64}", review["profile_digest"]) + or not isinstance(review.get("invocation_id"), str) or not review["invocation_id"] + or not isinstance(review.get("findings"), list) or len(review["findings"]) > 12 + or any( + not isinstance(finding, dict) or set(finding) != finding_fields + or not all(isinstance(value, str) and value and len(value) <= 240 and value == redact_diagnostic(value, limit=240) for key, value in finding.items() if key != "blocking") + or finding.get("severity") not in {"LOW", "MEDIUM", "HIGH", "CRITICAL"} + or finding.get("confidence") not in {"LOW", "MEDIUM", "HIGH"} + or finding.get("disposition") not in {"OPEN", "RESOLVED", "REJECTED", "NON_BLOCKING"} + or not isinstance(finding.get("blocking"), bool) + for finding in review["findings"] + ) + for review in state.assurance_reviews + ) + ): + raise StateError("checkpoint assurance review evidence is invalid") if not isinstance(state.terminal, bool) or state.terminal != (state.phase in {"COMPLETE", "BLOCKED", "FAILED"}): raise StateError("checkpoint terminal flag conflicts with phase") return state diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 5ffdade3..51c00b4a 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -1971,6 +1971,30 @@ function lifecycleQualityEvidence(step) { void localizeDynamicEvidence(dynamicRows); return section; } +function lifecycleAssuranceEvidence(step) { + const reviews = Array.isArray(step?.assurance_reviews) ? step.assurance_reviews : []; + if (!reviews.length) return null; + const section = document.createElement("section"); + section.className = "lifecycle-detail-modal__quality-evidence"; + const rounds = step?.repair_rounds || {}; + section.append(Object.assign(document.createElement("h3"), { textContent: t("lifecycle.detail_assurance") })); + if (Number.isFinite(Number(rounds.used))) section.append(Object.assign(document.createElement("p"), { + className: "estimate-meta", textContent: t("lifecycle.repair_rounds", { used: rounds.used, maximum: rounds.maximum || 3 }), + })); + const list = document.createElement("ol"); list.className = "lifecycle-detail-modal__phase-list"; + for (const review of reviews) { + if (!review || typeof review !== "object") continue; + const findings = Array.isArray(review.findings) ? review.findings : []; + const item = document.createElement("li"); + const role = String(review.reviewer || ""); const status = String(review.status || "UNRESOLVED"); + item.append(Object.assign(document.createElement("strong"), { textContent: `${reviewerLabel(role, role)} · ${status}` })); + const summary = findings.map((finding) => String(finding?.observation || "").trim()).filter(Boolean).join("; "); + item.append(Object.assign(document.createElement("span"), { textContent: summary || t("lifecycle.assurance_no_findings") })); + list.append(item); + } + if (!list.childElementCount) return null; + section.append(list); return section; +} function lifecycleRepairEvidence(step) { const audit = Array.isArray(step?.repair_audit) ? step.repair_audit : []; if (!audit.length) return null; @@ -2060,6 +2084,8 @@ function openLifecycleDetail(step, trigger) { content.append(phaseTiming); const qualityEvidence = lifecycleQualityEvidence(step); if (qualityEvidence) content.append(qualityEvidence); + const assuranceEvidence = lifecycleAssuranceEvidence(step); + if (assuranceEvidence) content.append(assuranceEvidence); const repairEvidence = lifecycleRepairEvidence(step); if (repairEvidence) content.append(repairEvidence); if (!modal.open) modal.showModal(); diff --git a/src/engineering_platform/assets/dashboard_locales.mjs b/src/engineering_platform/assets/dashboard_locales.mjs index 5625e08b..6ae2583c 100644 --- a/src/engineering_platform/assets/dashboard_locales.mjs +++ b/src/engineering_platform/assets/dashboard_locales.mjs @@ -3321,6 +3321,11 @@ Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_quality_evidence":"Uitge Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_quality_evidence":"Durchgeführte Qualitätsverbesserungen","lifecycle.quality_evidence.refactor":"Refaktorierung","lifecycle.quality_evidence.test_coverage":"Testabdeckung","lifecycle.quality_evidence.documentation":"Dokumentation","lifecycle.quality_evidence.validation":"Validierung","lifecycle.quality_evidence.no_change_required":"Keine Änderung erforderlich"}); Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.detail_quality_evidence":"Améliorations de qualité réalisées","lifecycle.quality_evidence.refactor":"Refactorisation","lifecycle.quality_evidence.test_coverage":"Couverture de tests","lifecycle.quality_evidence.documentation":"Documentation","lifecycle.quality_evidence.validation":"Validation","lifecycle.quality_evidence.no_change_required":"Aucune modification nécessaire"}); Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.detail_quality_evidence":"Mejoras de calidad realizadas","lifecycle.quality_evidence.refactor":"Refactorización","lifecycle.quality_evidence.test_coverage":"Cobertura de pruebas","lifecycle.quality_evidence.documentation":"Documentación","lifecycle.quality_evidence.validation":"Validación","lifecycle.quality_evidence.no_change_required":"No se requiere ningún cambio"}); +Object.assign(DASHBOARD_MESSAGES.en, {"lifecycle.detail_assurance":"Quality and security assurance","lifecycle.repair_rounds":"Repair rounds: {used}/{maximum}","lifecycle.assurance_no_findings":"No findings recorded"}); +Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_assurance":"Kwaliteits- en beveiligingscontrole","lifecycle.repair_rounds":"Herstelrondes: {used}/{maximum}","lifecycle.assurance_no_findings":"Geen bevindingen vastgelegd"}); +Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_assurance":"Qualitäts- und Sicherheitsprüfung","lifecycle.repair_rounds":"Reparaturrunden: {used}/{maximum}","lifecycle.assurance_no_findings":"Keine Befunde erfasst"}); +Object.assign(DASHBOARD_MESSAGES.fr, {"lifecycle.detail_assurance":"Assurance qualité et sécurité","lifecycle.repair_rounds":"Cycles de correction : {used}/{maximum}","lifecycle.assurance_no_findings":"Aucune conclusion enregistrée"}); +Object.assign(DASHBOARD_MESSAGES.es, {"lifecycle.detail_assurance":"Garantía de calidad y seguridad","lifecycle.repair_rounds":"Rondas de reparación: {used}/{maximum}","lifecycle.assurance_no_findings":"No se registraron hallazgos"}); Object.assign(DASHBOARD_MESSAGES.en, {"lifecycle.detail_repair_evidence":"Executed pull request check repairs","lifecycle.detail_repair_iteration":"Repair iteration {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Submitted for recheck","lifecycle.repair_outcome.agent_failed":"Agent repair failed","lifecycle.repair_outcome.agent_timed_out":"Agent repair timed out"}); Object.assign(DASHBOARD_MESSAGES.nl, {"lifecycle.detail_repair_evidence":"Uitgevoerd PR-controleherstel","lifecycle.detail_repair_iteration":"Hersteliteratie {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Ingediend voor hercontrole","lifecycle.repair_outcome.agent_failed":"Agentherstel mislukt","lifecycle.repair_outcome.agent_timed_out":"Agentherstel heeft de tijdslimiet overschreden"}); Object.assign(DASHBOARD_MESSAGES.de, {"lifecycle.detail_repair_evidence":"Durchgeführte PR-Prüfreparaturen","lifecycle.detail_repair_iteration":"Reparaturiteration {iteration}","lifecycle.repair_outcome.submitted_for_recheck":"Zur erneuten Prüfung eingereicht","lifecycle.repair_outcome.agent_failed":"Agentenreparatur fehlgeschlagen","lifecycle.repair_outcome.agent_timed_out":"Zeitlimit der Agentenreparatur überschritten"}); diff --git a/src/engineering_platform/capability_review.py b/src/engineering_platform/capability_review.py index dc71b044..a9e3e331 100644 --- a/src/engineering_platform/capability_review.py +++ b/src/engineering_platform/capability_review.py @@ -29,6 +29,8 @@ "finalization", ) REVIEWER_LABELS = { + "quality": "Quality Reviewer", + "security": "Security Reviewer", "apple_platform": "Apple Platform Reviewer", "windows_platform": "Windows Platform Reviewer", "home_assistant_integration": "Home Assistant Integration Reviewer", diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index d569121b..0ed5abe1 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -3,6 +3,7 @@ from __future__ import annotations import argparse +import hashlib from datetime import datetime, timezone from dataclasses import replace import json @@ -125,7 +126,10 @@ # A repair remains scoped to its original PR, but it must also have a finite # attempt budget. This prevents a persistently failing required check from # repeatedly invoking the provider without an operator decision. -MAX_PR_CHECK_REPAIR_ATTEMPTS = 3 +# One run-wide operational budget. It is intentionally not scoped to a +# provider, PR, candidate SHA, or lifecycle phase. +MAX_TOTAL_REPAIR_ROUNDS_PER_RUN = 3 +MAX_PR_CHECK_REPAIR_ATTEMPTS = MAX_TOTAL_REPAIR_ROUNDS_PER_RUN MAX_LOCAL_REPOSITORY_VALIDATION_ATTEMPTS = 3 @@ -1614,6 +1618,20 @@ def _run_local_repository_validation( local_validation_audit=(), ) for iteration in range(1, MAX_LOCAL_REPOSITORY_VALIDATION_ATTEMPTS + 1): + # The initial local check is not a repair. Every subsequent + # corrective validation dispatch consumes the same durable budget + # used by assurance, hosted-check and finalization repairs. + if iteration > 1: + if validation.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: + return self._save_terminal( + validation, "BLOCKED", "repair_budget_exhausted", + "Local validation still requires correction after the run-wide repair budget was exhausted.", + ), implementation + validation = replace(validation, repair_iterations=validation.repair_iterations + 1) + validation = self._record_repair_audit( + validation, failed_checks="validation: required local control", objective="Repair bounded local validation findings.", + result=None, outcome="planned", + ) try: profile = classify(changed_paths(self.root, "main")) except OSError: @@ -1669,6 +1687,12 @@ def _run_local_repository_validation( self.console_detail = error.console_detail validation = self._record_local_validation_audit(validation, result=None, outcome="agent_failed", profile=profile) return self._terminalize_provider_invocation_error(validation, error), implementation + if iteration > 1: + validation = self._record_repair_audit( + validation, failed_checks="validation: required local control", objective="Repair bounded local validation findings.", + result=result, + outcome="agent_failed" if result.terminal_state in {"BLOCKED", "FAILED"} else "submitted_for_recheck", + ) if result.terminal_state in {"BLOCKED", "FAILED"}: if ( result.terminal_state == "FAILED" @@ -1708,84 +1732,84 @@ def _run_local_repository_validation( ), implementation return self._save_terminal(validation, "BLOCKED", "local_validation_attempt_limit_reached", "Required local repository validation did not pass after 3 bounded iterations."), implementation - def _run_autonomous_quality_control( + def _run_quality_assurance( self, state: TransactionState, implementation: AgentResult ) -> tuple[TransactionState, AgentResult]: - """Run the required post-implementation refactor and quality boundary. + """Run independent, sandboxed quality and security reviews. - The controller is autonomous but cannot widen delivery scope: it may - amend only the current transaction branch and its existing PR. + This deliberately replaces the former mutating "quality control" + provider turn. Reviewers receive a pinned candidate and use the + provider's read-only sandbox; only ``_repair`` can subsequently + mutate the bounded branch. """ quality = replace( state, phase="QUALITY_CONTROL_AGENT", branch=implementation.branch or state.branch, pull_request=implementation.pull_request or state.pull_request, - next_action="autonomous_refactor_and_quality_control", + next_action="quality_and_security_review", ) + try: + candidate = self.repository.inspect(self.root) + except RunnerError: + return self._save_terminal(quality, "BLOCKED", "assurance_candidate_unavailable", "The assurance candidate could not be inspected."), implementation + if not candidate.clean or not re.fullmatch(r"[0-9a-f]{40}", candidate.head_sha): + return self._save_terminal(quality, "BLOCKED", "assurance_candidate_invalid", "Quality assurance requires one clean, pinned candidate."), implementation + profile_version = f"validation-profile@{VALIDATION_PROFILE_VERSION}" + profile_digest = "sha256:" + hashlib.sha256( + f"{profile_version}:{candidate.head_sha}".encode("utf-8") + ).hexdigest() + quality = replace(quality, assurance_profile={ + "version": profile_version, "digest": profile_digest, "candidate_sha": candidate.head_sha, + }) self.store.save(quality) write_live_status(self.root, quality, quality.next_action) - prompt = assemble_prompt( - Path(quality.prompt_path), quality, - managed_target=self.root if quality.execution_mode == "MANAGED" else None, - ) + """ - -Mandatory autonomous refactor and quality-control stage: -- Inspect the implementation now present on this transaction branch. -- Autonomously make only demonstrable maintainability, clarity, safety, or - test-coverage improvements within the original bounded objective. -- Assess test coverage for every changed behavior. Add or strengthen focused - regression tests whenever existing coverage does not prove that behavior. -- Assess the applicable operator, contract, and implementation documentation. - Update it whenever the bounded change affects documented behavior; only - leave documentation unchanged when the inspection proves it is unaffected. -- Run the relevant focused validation, including the added or affected tests, - and `git diff --check`. -- Preserve the existing transaction branch and pull request. If changes are - needed, commit and push them to that same branch; do not create another PR, - merge, alter authority, or expand scope. -- Return the same pull-request number and branch after the quality boundary. -- In quality_evidence, record only work actually performed in this stage. Use - activity values REFACTOR, TEST_COVERAGE, DOCUMENTATION, VALIDATION, or - NO_CHANGE_REQUIRED and a short safe result for each. Do not include raw - commands, output, prompts, source content, paths, secrets, or reasoning. -""" - try: - result = self._invoke_agent_with_timing(quality, prompt, quality=True) - quality = self._record_agent_execution_time(quality) - quality = self._record_validation_evidence(quality, result) - quality = replace(quality, quality_evidence=result.quality_evidence) - quality = self._record_verified_result_commit( - quality, - result, - phase="QUALITY_CONTROL_AGENT", - description="quality_control_commit_verified", - ) - self._persist_agent_usage(quality.run_id) - except ProviderReadinessBlocked as blocked: - return blocked.state, implementation - except CodexInvocationError as error: - quality = self._record_agent_execution_time(quality) - self.console_detail = error.console_detail - return self._terminalize_provider_invocation_error(quality, error), implementation - return self._advance_after_quality_control_agent_result(quality, implementation, result) - - def _advance_after_quality_control_agent_result( - self, quality: TransactionState, implementation: AgentResult, result: AgentResult, - ) -> tuple[TransactionState, AgentResult]: - """Apply live or recovered QC success without provider-session state.""" - if result.terminal_state in {"BLOCKED", "FAILED"}: - return self._save_terminal(quality, result.terminal_state, "autonomous_quality_control_failed", result.diagnostic or "Autonomous quality control did not complete."), implementation - if implementation.pull_request and result.pull_request and result.pull_request != implementation.pull_request: - return self._save_terminal(quality, "BLOCKED", "autonomous_quality_control_scope", "Autonomous quality control returned a different pull request."), implementation - if implementation.branch and result.branch and result.branch != implementation.branch: - return self._save_terminal(quality, "BLOCKED", "autonomous_quality_control_scope", "Autonomous quality control returned a different branch."), implementation - return quality, replace( - implementation, - branch=result.branch or implementation.branch, - pull_request=result.pull_request or implementation.pull_request, - validation_evidence=implementation.validation_evidence + result.validation_evidence, + evidence = ReviewerEvidence.from_repository(quality.run_id, quality.execution_mode, candidate) + selections = tuple( + ReviewerSelection(role, f"mandatory post-implementation {role} assurance", 1.0) + for role in ("quality", "security") ) + records: list[dict[str, object]] = [] + # Run sequentially: each is still a distinct sandboxed invocation, and + # this avoids sharing mutable CLI telemetry between parallel calls. + for selection in selections: + assurance_objective = ( + f"Mandatory {selection.reviewer} assurance. Profile {profile_version} ({profile_digest}); " + f"candidate {candidate.head_sha}. Report only concrete, bounded findings against the action acceptance criteria. " + + Path(quality.prompt_path).read_text(encoding="utf-8") + ) + result = run_reviews(self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] + try: + unchanged = self.repository.inspect(self.root) + except RunnerError: + unchanged = None + status = "UNRESOLVED" if result.failed or unchanged is None or unchanged.head_sha != candidate.head_sha else ("FAIL" if result.recommendations else "PASS") + findings = [ + { + "id": f"{selection.reviewer}-{index + 1}", "fingerprint": hashlib.sha256(note.encode("utf-8")).hexdigest()[:32], + "category": selection.reviewer.upper(), "criterion": "post_implementation_assurance", + "observation": redact_diagnostic(note, limit=240), "severity": "HIGH", "confidence": "MEDIUM", + "blocking": True, "disposition": "OPEN", + } + for index, note in enumerate(result.recommendations[:3]) + ] + records.append({ + "reviewer": selection.reviewer, "status": status, "candidate_sha": candidate.head_sha, + "profile_digest": profile_digest, "invocation_id": f"{quality.run_id}:{selection.reviewer}:{len(quality.assurance_reviews) + len(records)}", + "findings": findings, + }) + quality = replace(quality, assurance_reviews=quality.assurance_reviews + tuple(records)) + self.store.save(quality) + unresolved = [record for record in records if record["status"] == "UNRESOLVED"] + if unresolved: + return self._save_terminal(quality, "BLOCKED", "mandatory_assurance_unresolved", "A required quality or security review was unavailable, malformed, or candidate-mismatched."), implementation + findings = [finding for record in records for finding in record["findings"]] + if findings: + if quality.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: + return self._save_terminal(quality, "BLOCKED", "repair_budget_exhausted", "Mandatory assurance blockers remain after the run-wide repair budget was exhausted."), implementation + repaired = self._repair(quality, "quality/security review findings failed. Repair all listed bounded findings in one implementation repair round.") + return repaired, implementation + return quality, implementation def _reject_historical_agent_pull_request( self, state: TransactionState @@ -1845,9 +1869,11 @@ def _advance_after_primary_agent_result( state, result = self._run_local_repository_validation(state, result) if state.terminal: return state - state, result = self._run_autonomous_quality_control(state, result) + state, result = self._run_quality_assurance(state, result) if state.terminal: return state + if state.phase in {"REPAIR_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE"}: + return state return self._continue_after_quality_control(state, result, evidence) def _continue_after_quality_control( @@ -1883,9 +1909,10 @@ def _advance_after_recovered_provider_result( if lifecycle_phase == "EXECUTE_AGENT": return self._advance_after_primary_agent_result(state, result, evidence) if lifecycle_phase == "QUALITY_CONTROL_AGENT": - implementation = AgentResult("COMPLETE", branch=state.branch, pull_request=state.pull_request) - quality, implementation = self._advance_after_quality_control_agent_result(state, implementation, result) - return quality if quality.terminal else self._continue_after_quality_control(quality, implementation, evidence) + # The assurance phase has no mutating provider invocation to + # recover. A legacy interrupted quality-control run remains + # evidence-incomplete rather than being reinterpreted as PASS. + return self._save_terminal(state, "BLOCKED", "legacy_quality_recovery_unresolved", "Interrupted legacy quality control cannot satisfy the required read-only assurance.") if lifecycle_phase == "REPAIR_AGENT": return self._advance_after_repair_agent_result(state, result) if lifecycle_phase == "FINALIZE_AGENT": diff --git a/src/engineering_platform/execution_lifecycle.py b/src/engineering_platform/execution_lifecycle.py index 45ff5c69..6eb46a47 100644 --- a/src/engineering_platform/execution_lifecycle.py +++ b/src/engineering_platform/execution_lifecycle.py @@ -382,6 +382,13 @@ def belongs_to_step(step_id: str, phase_id: object, parent_phase_id: object, pha evidence = checkpoint.get("quality_evidence") if isinstance(evidence, (list, tuple)) and evidence: step["quality_evidence"] = list(evidence) + reviews = checkpoint.get("assurance_reviews") + if isinstance(reviews, (list, tuple)) and reviews: + step["assurance_reviews"] = list(reviews) + step["repair_rounds"] = { + "used": _nonnegative_int(checkpoint.get("repair_iterations")), + "maximum": 3, + } if step_id == "LOCAL_REPOSITORY_VALIDATION" and step["state"] not in {"PENDING", "SKIPPED"}: audit = checkpoint.get("local_validation_audit") if isinstance(audit, (list, tuple)) and audit: @@ -444,4 +451,5 @@ def belongs_to_step(step_id: str, phase_id: object, parent_phase_id: object, pha "required_validation_state": qualification.get("required_validation_state", "UNAVAILABLE"), "run_qualification": qualification.get("run_qualification", "UNAVAILABLE"), }, + "repair_rounds": {"used": _nonnegative_int(checkpoint.get("repair_iterations")), "maximum": 3}, } diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 1daa1220..93cfe403 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -3298,7 +3298,7 @@ def do_GET(self) -> None: # noqa: N802 except sqlite3.Error: self._send(503, {"error": "CENTRAL_UNAVAILABLE"}) return - artifact = re.fullmatch(r"/v1/projects/([^/]+)/artifacts/(terminal-evidence:[^/]+)", request.path) + artifact = re.fullmatch(r"/v1/projects/([^/]+)/artifacts/((?:terminal-evidence|assurance-findings):[^/]+)", request.path) if artifact: project_id, artifact_id = artifact.groups() authorization = self.headers.get("Authorization", "") diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index 22dbdfd2..3a55692e 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -333,7 +333,7 @@ def issue_consumer_credential(connection: sqlite3.Connection, *, consumer_id: st PRODUCER_READBACK_CONTRACT_VERSION = "1.1" -TERMINAL_EVIDENCE_CONTRACT_VERSION = "1.1" +TERMINAL_EVIDENCE_CONTRACT_VERSION = "1.2" _TERMINAL_OUTCOMES = frozenset({"COMPLETE", "BLOCKED", "FAILED"}) @@ -377,6 +377,10 @@ def _terminal_artifact_id(run_id: str) -> str: return f"terminal-evidence:{run_id}" +def _findings_artifact_id(run_id: str) -> str: + return f"assurance-findings:{run_id}" + + def _repository_revision(state: object, outcome: str) -> tuple[str | None, bool]: """Return a run-bound delivery revision, never an ambient checkout HEAD.""" if outcome != "COMPLETE": @@ -439,6 +443,23 @@ def write_terminal_evidence( revision, delivery_qualified = _repository_revision(checkpoint, outcome) artifact_id = _terminal_artifact_id(run_id) report_id = f"report:{run_id}" + reviews = list(checkpoint.assurance_reviews) + findings = [finding for review in reviews for finding in review.get("findings", [])] + findings_id = _findings_artifact_id(run_id) if checkpoint.assurance_profile is not None else None + if findings_id is not None: + findings_payload = { + "artifact_type": "EP_ASSURANCE_FINDINGS", "contract_version": "1.0", + "run_id": run_id, "project_id": str(row[1]), "repository_id": str(row[2]), + "profile": checkpoint.assurance_profile, "reviews": reviews, + } + findings_target = data_root / "artifacts" / "projects" / str(row[1]) / "runs" / run_id / "assurance-findings-v1.json" + findings_target.parent.mkdir(mode=0o700, parents=True, exist_ok=True) + findings_target.write_bytes(_canonical_json_bytes(findings_payload)) + findings_target.chmod(0o600) + record_artifact(repository_root, findings_target, artifact_id=findings_id, artifact_type="EP_ASSURANCE_FINDINGS", + content_type="application/json", created_at=_now(), run_id=run_id, submission_id=str(row[0]), + mission_id=str(row[10]) if row[10] is not None else None, producer_id=str(row[4]), + central_database=database, artifact_root=data_root / "artifacts") payload = { "artifact_type": "EP_TERMINAL_EVIDENCE", "contract_version": TERMINAL_EVIDENCE_CONTRACT_VERSION, "submission": {"id": str(row[0]), "project_id": str(row[1]), "repository_id": str(row[2]), @@ -454,6 +475,14 @@ def write_terminal_evidence( "validation": list(checkpoint.validation_evidence), "quality": list(checkpoint.quality_evidence), "repair": list(checkpoint.repair_audit), "finalization": checkpoint.latest_repository_evidence, }, + "assurance": { + "status": "NOT_RECORDED" if checkpoint.assurance_profile is None else ("PASS" if all(review.get("status") == "PASS" for review in reviews) else "UNRESOLVED" if any(review.get("status") == "UNRESOLVED" for review in reviews) else "FAIL"), + "profile": checkpoint.assurance_profile, + "quality_review": next((review.get("status") for review in reversed(reviews) if review.get("reviewer") == "quality"), "NOT_RECORDED"), + "security_review": next((review.get("status") for review in reversed(reviews) if review.get("reviewer") == "security"), "NOT_RECORDED"), + "repair_rounds": {"used": checkpoint.repair_iterations, "maximum": 3}, + "findings": {"open_blocking": sum(1 for finding in findings if finding.get("blocking") and finding.get("disposition") == "OPEN"), "open_non_blocking": sum(1 for finding in findings if not finding.get("blocking") and finding.get("disposition") == "OPEN"), "artifact": None if findings_id is None else {"id": findings_id}}, + }, } target = data_root / "artifacts" / "projects" / str(row[1]) / "runs" / run_id / "terminal-evidence-v1.json" target.parent.mkdir(mode=0o700, parents=True, exist_ok=True) @@ -608,12 +637,12 @@ def producer_readback( def producer_evidence_artifact( connection: sqlite3.Connection, *, project_id: str, artifact_id: str, ) -> bytes | None: - """Return only a verified, project-scoped terminal evidence payload.""" + """Return a verified, project-scoped terminal or assurance artifact.""" row = connection.execute( """SELECT a.digest_algorithm,a.digest,a.storage_location FROM execution_artifact_records a JOIN ep_parity_lifecycle_dispatches d ON d.run_id=a.run_id - WHERE d.project_id=? AND a.artifact_id=? AND a.artifact_type='EP_TERMINAL_EVIDENCE'""", + WHERE d.project_id=? AND a.artifact_id=? AND a.artifact_type IN ('EP_TERMINAL_EVIDENCE','EP_ASSURANCE_FINDINGS')""", (project_id, artifact_id), ).fetchone() if row is None or row[0] != "sha256" or not isinstance(row[1], str): diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 5e5047e2..e3e6a307 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -174,6 +174,10 @@ def available(self) -> bool: def version(self) -> str: return "0.146.0" + def review(self, _: Path, selection: object, __: str, evidence: object = None) -> ReviewerResult: + """Default post-implementation assurance fixture: clean, read-only pass.""" + return ReviewerResult(getattr(selection, "reviewer"), "No blocking finding.") + class ReviewCapableFakeAgent(FakeAgent): def __init__(self, result: AgentResult) -> None: @@ -1553,12 +1557,9 @@ def test_new_run_initializes_and_records_canonical_prompt(self) -> None: self.assertIn("do not rerun the development-host bootstrap", agent.prompts[0]) self.assertIn(f"The only repository checkout for this transaction is `{self.root.resolve()}`", agent.prompts[0]) self.assertIn("producer provenance only", agent.prompts[0]) - self.assertEqual(agent.roots, [self.root, self.root]) - self.assertIn("Mandatory autonomous refactor and quality-control stage", agent.prompts[1]) - self.assertIn("Assess test coverage for every changed behavior", agent.prompts[1]) - self.assertIn("Assess the applicable operator, contract, and implementation documentation", agent.prompts[1]) - self.assertIn("In quality_evidence, record only work actually performed", agent.prompts[1]) - self.assertEqual(state.quality_evidence, quality_evidence) + self.assertEqual(agent.roots, [self.root]) + self.assertEqual([review["status"] for review in state.assurance_reviews], ["PASS", "PASS"]) + self.assertEqual(state.assurance_profile["candidate_sha"], "a" * 40) self.assertEqual(repository.synchronize_calls, [self.root]) def test_provider_recovery_preflight_rejects_every_ambiguous_restart_condition(self) -> None: @@ -1902,7 +1903,7 @@ def test_managed_run_keeps_a_producer_target_from_overriding_host_checkout(self) self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None ).run(self.prompt, run_id="managed-target-boundary") - self.assertEqual(agent.roots, [self.root, self.root]) + self.assertEqual(agent.roots, [self.root]) self.assertIn( f"The only repository checkout for this transaction is `{self.root.resolve()}`", agent.prompts[0], @@ -2326,21 +2327,24 @@ def test_execute_agent_phase_is_published_before_agent_invocation(self) -> None: agent = LiveStatusFakeAgent(AgentResult("COMPLETE")) runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) runner.run(self.prompt, run_id="live-phase-run") - self.assertEqual(agent.live_phase, "QUALITY_CONTROL_AGENT") - self.assertEqual(agent.live_action, "autonomous_refactor_and_quality_control") + self.assertEqual(agent.live_phase, "EXECUTE_AGENT") + self.assertEqual(agent.live_action, "invoke_agent") self.assertEqual(agent.activity_action, "Codex bewerkt bestanden") - def test_autonomous_quality_control_cannot_replace_the_implementation_pr(self) -> None: + def test_quality_assurance_does_not_create_or_replace_the_implementation_pr(self) -> None: agent = SequencedFakeAgent([ AgentResult("COMPLETE", "codex/implementation", 701), AgentResult("COMPLETE", "codex/implementation", 702), ]) state = EngineeringRunner( - self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None + self.root, self.store, FakeRepository(), FakeGitHub([ + PullRequestEvidence(701, "OPEN", True, True, head_branch="codex/implementation", base_branch="main"), + ]), agent, lambda _: None ).run(self.prompt, run_id="quality-scope-run") - self.assertEqual(state.phase, "BLOCKED") - self.assertEqual(state.next_action, "autonomous_quality_control_scope") + self.assertEqual(state.phase, "WAIT_FOR_OPERATOR_MERGE") + self.assertEqual(len(agent.prompts), 1) + self.assertEqual([review["status"] for review in state.assurance_reviews], ["PASS", "PASS"]) def test_local_repository_validation_iterates_before_creating_the_implementation_pr(self) -> None: agent = SequencedFakeAgent([ @@ -2440,7 +2444,7 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: self.assertFalse(state.terminal) self.assertEqual(state.local_validation_iterations, 1) self.assertEqual(state.local_validation_audit[0]["outcome"], "validated") - self.assertEqual(len(agent.prompts), 3) + self.assertEqual(len(agent.prompts), 2) self.assertIn("Local repository validation gate — iteration 1 of 3", agent.prompts[1]) def test_unverified_or_external_implementation_failure_never_starts_local_repair(self) -> None: diff --git a/tests/engineering/test_execution_lifecycle.py b/tests/engineering/test_execution_lifecycle.py index ae552efe..b94181ac 100644 --- a/tests/engineering/test_execution_lifecycle.py +++ b/tests/engineering/test_execution_lifecycle.py @@ -68,6 +68,22 @@ def test_local_validation_is_a_managed_step_with_its_own_iteration_evidence(self self.assertEqual(by_id["LOCAL_REPOSITORY_VALIDATION"]["iteration_count"], 2) self.assertEqual(by_id["LOCAL_REPOSITORY_VALIDATION"]["repair_audit"], list(audit)) + def test_assurance_projects_two_pinned_reviews_and_one_run_wide_counter(self) -> None: + profile = {"version": "validation-profile@1", "digest": "sha256:" + "a" * 64, "candidate_sha": "b" * 40} + finding = {"id": "security-1", "fingerprint": "f" * 32, "category": "SECURITY", "criterion": "post_implementation_assurance", "observation": "Missing project isolation test.", "severity": "HIGH", "confidence": "MEDIUM", "blocking": True, "disposition": "OPEN"} + reviews = ( + {"reviewer": "quality", "status": "PASS", "candidate_sha": "b" * 40, "profile_digest": profile["digest"], "invocation_id": "quality-1", "findings": []}, + {"reviewer": "security", "status": "FAIL", "candidate_sha": "b" * 40, "profile_digest": profile["digest"], "invocation_id": "security-1", "findings": [finding]}, + ) + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) + self._state(root, "QUALITY_CONTROL_AGENT", repair_iterations=2, assurance_profile=profile, assurance_reviews=reviews) + value = projection(root, "inbox-flow") + quality = next(step for step in value["steps"] if step["id"] == "QUALITY_CONTROL_AGENT") + self.assertEqual(quality["repair_rounds"], {"used": 2, "maximum": 3}) + self.assertEqual(quality["assurance_reviews"], list(reviews)) + self.assertEqual(value["repair_rounds"], {"used": 2, "maximum": 3}) + def test_genesis_has_its_own_canonical_path(self) -> None: self.assertNotIn("WAIT_FOR_OPERATOR_MERGE", intended_path("GENESIS")) self.assertIn("WAIT_FOR_OPERATOR_MERGE", intended_path("MANAGED")) From 5e45e8aa24dfbf13d60361b8c5241819755df88f Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 21:50:41 +0200 Subject: [PATCH 02/87] test: cover assurance evidence readback --- tests/engineering/test_submission_service.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 11d369cc..c0a059ca 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -94,7 +94,13 @@ def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evid connection.execute("INSERT INTO ep_execution_runs(run_id,project_id,state,created_at,updated_at,execution_mode) VALUES(?,?,?,?,?,?)", ("run-readback", "djconnect", "COMPLETE", "now", "now", "MANAGED")) connection.execute("INSERT INTO execution_runs(run_id,execution_date,arrived_at,execution_started_at,execution_finished_at,queue_wait_seconds,execution_seconds,terminal_state,input_tokens,output_tokens,total_tokens,execution_mode,workspace,repository,execution_host_version) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", ("run-readback", "2026-01-01", "now", "now", "now", 0, 0, "COMPLETE", None, None, None, "MANAGED", "djconnect", "djconnect", "test")) connection.execute("INSERT INTO ep_parity_lifecycle_dispatches(submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution) VALUES(?,?,?,?,?,?,?,?,?)", (submission_id, "djconnect", "djconnect", "run-readback", "COMPLETE", "/private/prompt", "now", "now", "NONE")) - checkpoint = TransactionState(run_id="run-readback", repository="djconnect", prompt_path="prompt", phase="COMPLETE", terminal=True, action_intent="VALIDATION_ONLY") + profile = {"version": "validation-profile@1", "digest": "sha256:" + "b" * 64, "candidate_sha": "c" * 40} + finding = {"id": "security-1", "fingerprint": "d" * 32, "category": "SECURITY", "criterion": "post_implementation_assurance", "observation": "Project isolation lacks a negative test.", "severity": "HIGH", "confidence": "MEDIUM", "blocking": True, "disposition": "OPEN"} + reviews = ( + {"reviewer": "quality", "status": "PASS", "candidate_sha": "c" * 40, "profile_digest": profile["digest"], "invocation_id": "quality-1", "findings": []}, + {"reviewer": "security", "status": "FAIL", "candidate_sha": "c" * 40, "profile_digest": profile["digest"], "invocation_id": "security-1", "findings": [finding]}, + ) + checkpoint = TransactionState(run_id="run-readback", repository="djconnect", prompt_path="prompt", phase="COMPLETE", terminal=True, action_intent="VALIDATION_ONLY", assurance_profile=profile, assurance_reviews=reviews, repair_iterations=2) connection.execute("INSERT INTO engineering_transactions(run_id,payload,phase,updated_at) VALUES(?,?,?,?)", ("run-readback", json.dumps(checkpoint.to_dict()), "COMPLETE", "now")) connection.execute("INSERT INTO prompt_execution_history(run_id,terminal_state,prompt_title,executed_at,git_commit,report_path,updated_at) VALUES(?,?,?,?,?,?,?)", ("run-readback", "COMPLETE", "safe", "now", None, "/private/report", "now")) artifact_id = submission_service.write_terminal_evidence(self.root, repository_root=self.root, run_id="run-readback") @@ -104,6 +110,8 @@ def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evid stored_artifact = submission_service.producer_evidence_artifact(connection, project_id="djconnect", artifact_id=artifact_id) self.assertIsNotNone(stored_artifact) self.assertEqual(json.loads(stored_artifact or b"{}")['run']['id'], "run-readback") + findings_artifact = submission_service.producer_evidence_artifact(connection, project_id="djconnect", artifact_id="assurance-findings:run-readback") + self.assertEqual(json.loads(findings_artifact or b"{}")["reviews"], list(reviews)) with urlopen(Request(endpoint, headers={"Authorization": f"Bearer {self.credential}"})) as response: # nosec B310 terminal = json.loads(response.read()) self.assertEqual(terminal["run"]["id"], "run-readback") @@ -116,6 +124,8 @@ def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evid artifact = json.loads(response.read()) self.assertEqual(artifact["submission"]["id"], submission_id) self.assertEqual(artifact["run"]["id"], "run-readback") + self.assertEqual(artifact["assurance"]["repair_rounds"], {"used": 2, "maximum": 3}) + self.assertEqual(artifact["assurance"]["findings"]["artifact"]["id"], "assurance-findings:run-readback") # The projection is CENTRAL state, not a process-local cache: a Server # restart preserves the exact submission/run/evidence correlation. From 970888af8e8093f07b2d4aa106165d28911823f7 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:01:56 +0200 Subject: [PATCH 03/87] fix: apply assurance to genesis runs --- src/engineering_platform/execution_host.py | 14 ++++++++++---- tests/engineering/test_execution_host.py | 2 ++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 0ed5abe1..63b0ffc1 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1733,7 +1733,7 @@ def _run_local_repository_validation( return self._save_terminal(validation, "BLOCKED", "local_validation_attempt_limit_reached", "Required local repository validation did not pass after 3 bounded iterations."), implementation def _run_quality_assurance( - self, state: TransactionState, implementation: AgentResult + self, state: TransactionState, implementation: AgentResult, *, assurance_root: Path | None = None, ) -> tuple[TransactionState, AgentResult]: """Run independent, sandboxed quality and security reviews. @@ -1750,7 +1750,7 @@ def _run_quality_assurance( next_action="quality_and_security_review", ) try: - candidate = self.repository.inspect(self.root) + candidate = self.repository.inspect(assurance_root or self.root) except RunnerError: return self._save_terminal(quality, "BLOCKED", "assurance_candidate_unavailable", "The assurance candidate could not be inspected."), implementation if not candidate.clean or not re.fullmatch(r"[0-9a-f]{40}", candidate.head_sha): @@ -1778,9 +1778,9 @@ def _run_quality_assurance( f"candidate {candidate.head_sha}. Report only concrete, bounded findings against the action acceptance criteria. " + Path(quality.prompt_path).read_text(encoding="utf-8") ) - result = run_reviews(self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] + result = run_reviews(assurance_root or self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] try: - unchanged = self.repository.inspect(self.root) + unchanged = self.repository.inspect(assurance_root or self.root) except RunnerError: unchanged = None status = "UNRESOLVED" if result.failed or unchanged is None or unchanged.head_sha != candidate.head_sha else ("FAIL" if result.recommendations else "PASS") @@ -1860,6 +1860,12 @@ def _advance_after_primary_agent_result( ) -> TransactionState: """Shared post-provider transition for live and recovered results.""" if state.execution_mode == "GENESIS": + target = Path(result.repository_path).expanduser() if result.repository_path else None + if not target or not target.is_absolute() or target_repository_authorization(self.root, target): + return self._reconcile_genesis_result(state, result) + state, result = self._run_quality_assurance(state, result, assurance_root=target) + if state.terminal or state.phase in {"REPAIR_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE"}: + return state return self._reconcile_genesis_result(state, result) recoverable_local_failure = self._is_recoverable_implementation_validation_failure(state, result) if state.transaction_kind == "IMPLEMENTATION" and state.action_intent == "MUTATING_DELIVERY" and ( diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index e3e6a307..624f1f79 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -2939,6 +2939,8 @@ def test_genesis_mode_reconciles_a_clean_local_commit_without_remote_or_pr(self) self.assertEqual(state.genesis_repository_path, str(target)) self.assertEqual(state.genesis_commit_sha, commit) 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"]) def test_genesis_selects_its_target_before_managed_cleanliness_checks(self) -> None: target = self.root.parent / f"genesis-clean-{self.root.name}" From 75a6a719d2e922610f79baa67eaeee390e241b70 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:16:15 +0200 Subject: [PATCH 04/87] fix: support local genesis assurance candidates --- src/engineering_platform/execution_host.py | 26 ++++++++++++++++++++-- tests/engineering/test_execution_host.py | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 63b0ffc1..43c2fbf6 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1749,8 +1749,9 @@ def _run_quality_assurance( pull_request=implementation.pull_request or state.pull_request, next_action="quality_and_security_review", ) + candidate_root = assurance_root or self.root try: - candidate = self.repository.inspect(assurance_root or self.root) + candidate = self._inspect_assurance_candidate(candidate_root, state.execution_mode) except RunnerError: return self._save_terminal(quality, "BLOCKED", "assurance_candidate_unavailable", "The assurance candidate could not be inspected."), implementation if not candidate.clean or not re.fullmatch(r"[0-9a-f]{40}", candidate.head_sha): @@ -1780,7 +1781,7 @@ def _run_quality_assurance( ) result = run_reviews(assurance_root or self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] try: - unchanged = self.repository.inspect(assurance_root or self.root) + unchanged = self._inspect_assurance_candidate(candidate_root, state.execution_mode) except RunnerError: unchanged = None status = "UNRESOLVED" if result.failed or unchanged is None or unchanged.head_sha != candidate.head_sha else ("FAIL" if result.recommendations else "PASS") @@ -1811,6 +1812,27 @@ def _run_quality_assurance( return repaired, implementation return quality, implementation + def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> RepositoryEvidence: + """Return a pinned review candidate without weakening Managed evidence. + + Genesis deliberately supports a local-only repository. Its assurance + candidate must therefore be inspectable without Managed's canonical + bootstrap document or an ``origin`` remote. + """ + try: + return self.repository.inspect(root) + except RunnerError: + if execution_mode != "GENESIS" or not (root / ".git").exists(): + raise + provider = getattr(self.repository, "provider", GitProvider()) + try: + branch = provider.command(root, "git", "branch", "--show-current") + head_sha = provider.command(root, "git", "rev-parse", "HEAD") + clean = not provider.command(root, "git", "status", "--porcelain", "--untracked-files=all") + except RuntimeError as error: + raise RunnerError(str(error)) from error + return RepositoryEvidence(root.name, branch, head_sha, clean, True) + def _reject_historical_agent_pull_request( self, state: TransactionState ) -> TransactionState | None: diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 624f1f79..cacb2f0e 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1605,7 +1605,7 @@ def test_optional_phase_telemetry_and_agent_timing_never_change_run_authority(se with patch("engineering_platform.execution_host._complete_phase", side_effect=execution_host.EngineeringStorageError("offline")): execution_host.complete_phase(self.root, SimpleNamespace()) agent = FakeAgent(AgentResult("WAITING")) - runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + runner = EngineeringRunner(self.root, self.store, SubprocessRepositoryClient(), FakeGitHub([]), agent, lambda _: None) state = TransactionState("timing-run", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT") for measured in (True, "unknown", -1, 86_401): agent.last_execution_seconds = measured From f60818b71370d3367ffbafbadf591beb1d3ebf77 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:16:15 +0200 Subject: [PATCH 05/87] fix: support local genesis assurance candidates --- src/engineering_platform/execution_host.py | 25 ++++++++++++++++++++-- tests/engineering/test_execution_host.py | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 63b0ffc1..00701c66 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1749,8 +1749,9 @@ def _run_quality_assurance( pull_request=implementation.pull_request or state.pull_request, next_action="quality_and_security_review", ) + candidate_root = assurance_root or self.root try: - candidate = self.repository.inspect(assurance_root or self.root) + candidate = self._inspect_assurance_candidate(candidate_root, state.execution_mode) except RunnerError: return self._save_terminal(quality, "BLOCKED", "assurance_candidate_unavailable", "The assurance candidate could not be inspected."), implementation if not candidate.clean or not re.fullmatch(r"[0-9a-f]{40}", candidate.head_sha): @@ -1780,7 +1781,7 @@ def _run_quality_assurance( ) result = run_reviews(assurance_root or self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] try: - unchanged = self.repository.inspect(assurance_root or self.root) + unchanged = self._inspect_assurance_candidate(candidate_root, state.execution_mode) except RunnerError: unchanged = None status = "UNRESOLVED" if result.failed or unchanged is None or unchanged.head_sha != candidate.head_sha else ("FAIL" if result.recommendations else "PASS") @@ -1811,6 +1812,26 @@ def _run_quality_assurance( return repaired, implementation return quality, implementation + def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> RepositoryEvidence: + """Return a pinned review candidate without weakening Managed evidence. + + Genesis deliberately supports a local-only repository. Its assurance + candidate must therefore be inspectable without Managed's canonical + bootstrap document or an ``origin`` remote. + """ + if execution_mode != "GENESIS": + return self.repository.inspect(root) + if not (root / ".git").exists(): + raise RunnerError("this is not a local Git repository") + provider = getattr(self.repository, "provider", GitProvider()) + try: + branch = provider.command(root, "git", "branch", "--show-current") + head_sha = provider.command(root, "git", "rev-parse", "HEAD") + clean = not provider.command(root, "git", "status", "--porcelain", "--untracked-files=all") + except RuntimeError as error: + raise RunnerError(str(error)) from error + return RepositoryEvidence(root.name, branch, head_sha, clean, True) + def _reject_historical_agent_pull_request( self, state: TransactionState ) -> TransactionState | None: diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 624f1f79..cacb2f0e 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1605,7 +1605,7 @@ def test_optional_phase_telemetry_and_agent_timing_never_change_run_authority(se with patch("engineering_platform.execution_host._complete_phase", side_effect=execution_host.EngineeringStorageError("offline")): execution_host.complete_phase(self.root, SimpleNamespace()) agent = FakeAgent(AgentResult("WAITING")) - runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + runner = EngineeringRunner(self.root, self.store, SubprocessRepositoryClient(), FakeGitHub([]), agent, lambda _: None) state = TransactionState("timing-run", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT") for measured in (True, "unknown", -1, 86_401): agent.last_execution_seconds = measured From 0b086fb2796850c90bc347ff6bc6ad5c858b7ca4 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:28:23 +0200 Subject: [PATCH 06/87] fix: preserve qualification retry and runtime cleanliness --- src/engineering_platform/lifecycle_worker.py | 5 ++++- .../parity_lifecycle_dispatcher.py | 5 ++++- src/engineering_platform/platform_bootstrap.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/engineering_platform/lifecycle_worker.py b/src/engineering_platform/lifecycle_worker.py index 85a464e0..b0ee70f8 100644 --- a/src/engineering_platform/lifecycle_worker.py +++ b/src/engineering_platform/lifecycle_worker.py @@ -104,7 +104,10 @@ def eligible_submission_ids(self) -> list[str]: (prior.state IN ('CLAIMED','RUNNING') AND prior.submission_id!=s.submission_id) OR (prior.state IN ('BLOCKED','FAILED') AND prior.operator_resolution='OPEN' AND prior.submission_id!=s.submission_id) - OR (prior.operator_resolution='RETRIED' AND prior.resolution_submission_id!=s.submission_id) + OR (prior.operator_resolution='RETRIED' AND prior.resolution_submission_id!=s.submission_id + AND NOT EXISTS (SELECT 1 FROM ep_parity_lifecycle_dispatches retry + WHERE retry.submission_id=prior.resolution_submission_id + AND retry.operator_resolution='RETRIED')) ) ) ORDER BY s.project_id, diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index bc629178..bfd9ee58 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -279,7 +279,10 @@ def _claim(self, submission_id: str) -> tuple[ParityProjectContext, HistoricalCa WHERE project_id=? AND ( state IN ('CLAIMED','RUNNING') OR (state IN ('BLOCKED','FAILED') AND operator_resolution='OPEN') - OR (operator_resolution='RETRIED' AND resolution_submission_id!=?) + OR (operator_resolution='RETRIED' AND resolution_submission_id!=? + AND NOT EXISTS (SELECT 1 FROM ep_parity_lifecycle_dispatches retry + WHERE retry.submission_id=ep_parity_lifecycle_dispatches.resolution_submission_id + AND retry.operator_resolution='RETRIED')) ) LIMIT 1""", (context.project_id, submission_id), ).fetchone() diff --git a/src/engineering_platform/platform_bootstrap.py b/src/engineering_platform/platform_bootstrap.py index 3a2c883b..31bc122a 100644 --- a/src/engineering_platform/platform_bootstrap.py +++ b/src/engineering_platform/platform_bootstrap.py @@ -356,6 +356,21 @@ def _provision_workspace_paths(root: Path, workspace: Path) -> dict[str, Path]: paths["runs"] = workspace / "engineering-runs" for path in paths.values(): path.mkdir(mode=0o700, parents=True, exist_ok=True) + # A shared runtime workspace may be represented in a checkout by the + # platform-owned `.engineering` symlink. Keep that implementation detail + # out of Git's untracked-worktree evidence; tracked project files are + # unaffected by an info/exclude rule. + local_workspace = root / WORKSPACE_DIRECTORY + git_directory = root / ".git" + if local_workspace.is_symlink() and git_directory.is_dir() and local_workspace.resolve() == workspace.resolve(): + exclude = git_directory / "info" / "exclude" + try: + existing = exclude.read_text(encoding="utf-8") if exclude.exists() else "" + if ".engineering/" not in existing.splitlines(): + exclude.parent.mkdir(mode=0o700, parents=True, exist_ok=True) + exclude.write_text(existing.rstrip("\n") + "\n.engineering/\n", encoding="utf-8") + except OSError: + pass return paths From 9053f4d772cef15d97d73c2f3e81db70eeb10a0e Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:33:42 +0200 Subject: [PATCH 07/87] docs: define central queue operator dispositions --- .../engineering/EXECUTION_HOST_ARCHITECTURE.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index 02f1cbfd..0aeb97ad 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -66,6 +66,24 @@ previous lease history. EP serializes mutating work at the repository/execution-scope boundary: no more than one mutating execution may own a scope at once. FIFO is the default queue + +## CENTRAL queue operator handling + +CENTRAL owns the durable queue record after admission; Forge remains the +authority for the originating Action intent. Consequently, an Operations +Console operator must never delete a queued submission or silently make it +disappear. Queue handling is an explicit, per-submission, reasoned state +transition with an append-only audit event and producer-visible readback. + +The supported operator dispositions are `DEFERRED`, `QUARANTINED`, and +`DECLINED`; only `DEFERRED` and `QUARANTINED` may be explicitly resumed to +`QUEUED`. A declined submission is terminal but retained with its correlation +and reason. A lifecycle worker selects only admitted `QUEUED` submissions. + +Forge observes these dispositions through the canonical producer-readback +contract and reconciles its own Action state. EP does not invoke Forge +internals, mutate Forge storage, or infer cancellation. Until a versioned +Forge callback contract exists, readback is the required reconciliation path. ordering within that scope, but admission and selection remain policy-driven; FIFO is not a second planning authority. The active mutation lease starts with the accepted execution and is retained through provider work, validation, From e1b53218c1659198f511332a66d97b7a25799787 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:43:33 +0200 Subject: [PATCH 08/87] feat: add audited central queue disposition --- src/engineering_platform/assets/dashboard.js | 21 +++++- src/engineering_platform/server.py | 69 +++++++++++++++++-- .../submission_service.py | 26 +++++++ tests/engineering/test_submission_service.py | 16 +++++ 4 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 51c00b4a..a6483361 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -935,7 +935,7 @@ function queueItems(x, queueDepth) { ? locale.dateTime(new Date(modified)) : t("format.timestamp_unavailable"), }); - const defer = item.queue_source === "CENTRAL" ? null : document.createElement("button"); + const defer = document.createElement("button"); if (defer) { defer.className = "queue-defer"; defer.type = "button"; @@ -945,7 +945,9 @@ function queueItems(x, queueDepth) { defer.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); - deferQueueItem(item, defer); + if (item.queue_source === "CENTRAL") { + queueDisposition(item, "DEFERRED", "Operator deferred this submission from Operations Console.", defer); + } else deferQueueItem(item, defer); }); } body.append(title, meta); @@ -954,6 +956,21 @@ function queueItems(x, queueDepth) { container.append(row); }); } +function queueDisposition(item, disposition, reason, button) { + const submissionId = String(item?.submission_id || item?.filename || ""); + if (!submissionId) return; + confirmDashboardAction(t("queue.defer_title"), t("queue.defer_description", { title: submissionId }), t("queue.defer_action")) + .then((confirmed) => { + if (!confirmed) return; + button.disabled = true; + return fetch("/api/queue-disposition", { method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ submission_id: submissionId, disposition, reason }) }) + .then(async (response) => ({ ok: response.ok, body: await response.json().catch(() => ({})) })) + .then((result) => { if (!result.ok) throw Error(result.body.error || t("queue.defer_failed")); return refreshDashboard(); }) + .catch((error) => showDashboardError(error.message, t("queue.defer_failed"))) + .finally(() => { button.disabled = false; }); + }); +} function deferQueueItem(item, button) { const filename = String(item?.filename || ""); if (!filename) return; diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 93cfe403..57be6ae9 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -92,7 +92,7 @@ # bootstrap is deliberately separate from the retired predecessor migration # machinery: it creates a clean installation only and never accepts a source # database path. -SERVER_STORE_SCHEMA_VERSION = 53 +SERVER_STORE_SCHEMA_VERSION = 54 SERVER_ENVIRONMENT_DATA_ROOT = "EP_SERVER_DATA_ROOT" FILE_INBOX_DIRECTORY = "file-inbox" HTTP_JSON_OPENAPI_PATH = "/openapi.json" @@ -703,7 +703,7 @@ def _migrate_schema_43(connection: sqlite3.Connection) -> None: transport TEXT NOT NULL CHECK(transport IN ('HTTP','CLI','FILE_INBOX','LEGACY_FILE')), prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL, idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT, - state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED')), admission TEXT NOT NULL, + state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED','DEFERRED','QUARANTINED','DECLINED')), admission TEXT NOT NULL, created_at TEXT NOT NULL)""") connection.execute("CREATE INDEX ep_submissions_project_lookup ON ep_submissions(project_id,state,created_at DESC)") connection.execute("CREATE UNIQUE INDEX ep_submissions_idempotency_lookup ON ep_submissions(project_id,idempotency_key) WHERE idempotency_key IS NOT NULL") @@ -920,7 +920,7 @@ def _migrate_schema_51(connection: sqlite3.Connection) -> None: prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL, idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT, transport_receipt_id TEXT, transport_received_at TEXT, - state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED')), admission TEXT NOT NULL, + state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED','DEFERRED','QUARANTINED','DECLINED')), admission TEXT NOT NULL, created_at TEXT NOT NULL)""") connection.execute("""INSERT INTO ep_submissions( submission_id,project_id,repository_id,producer_id,producer_type,producer_version,transport,prompt,prompt_digest,constraints,idempotency_key,correlation_id,mission_id,engineering_action_id,transport_receipt_id,transport_received_at,state,admission,created_at) @@ -1167,6 +1167,38 @@ def _migrate_schema_53(connection: sqlite3.Connection) -> None: connection.execute("UPDATE ep_installations SET schema_version=53") +def _migrate_schema_54(connection: sqlite3.Connection) -> None: + """Widen CENTRAL submission state for audited operator handling.""" + connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema53") + connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52,53,54)))") + connection.execute("INSERT INTO ep_installations SELECT instance_id,created_at,54 FROM ep_installations_schema53") + connection.execute("DROP TABLE ep_installations_schema53") + for table in ("ep_submission_events", "ep_submission_prompt_history", "ep_parity_lifecycle_dispatches", "ep_submissions"): + connection.execute(f"ALTER TABLE {table} RENAME TO {table}_schema53") + connection.execute("""CREATE TABLE ep_submissions ( + submission_id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id), + repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id), producer_id TEXT NOT NULL, + producer_type TEXT NOT NULL, producer_version TEXT, transport TEXT NOT NULL CHECK(transport IN ('HTTP','CLI','FILE_INBOX','DEPENDABOT','LEGACY_FILE')), + prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL, idempotency_key TEXT, correlation_id TEXT, + mission_id TEXT, engineering_action_id TEXT, transport_receipt_id TEXT, transport_received_at TEXT, + state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED','DEFERRED','QUARANTINED','DECLINED')), admission TEXT NOT NULL, created_at TEXT NOT NULL)""") + connection.execute("INSERT INTO ep_submissions SELECT * FROM ep_submissions_schema53") + connection.execute("CREATE TABLE ep_submission_events (event_id INTEGER PRIMARY KEY, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), event_kind TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)") + connection.execute("INSERT INTO ep_submission_events SELECT * FROM ep_submission_events_schema53") + connection.execute("CREATE TABLE ep_submission_prompt_history (submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), prompt_digest TEXT NOT NULL, recorded_at TEXT NOT NULL)") + connection.execute("INSERT INTO ep_submission_prompt_history SELECT * FROM ep_submission_prompt_history_schema53") + connection.execute("""CREATE TABLE ep_parity_lifecycle_dispatches (submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), project_id TEXT NOT NULL REFERENCES ep_project_registrations(project_id), repository_id TEXT NOT NULL REFERENCES ep_repository_registrations(repository_id), run_id TEXT NOT NULL UNIQUE REFERENCES ep_execution_runs(run_id), state TEXT NOT NULL CHECK(state IN ('CLAIMED','RUNNING','COMPLETE','BLOCKED','FAILED')), prompt_path TEXT NOT NULL, claimed_at TEXT NOT NULL, updated_at TEXT NOT NULL, operator_resolution TEXT NOT NULL DEFAULT 'NONE' CHECK(operator_resolution IN ('NONE','OPEN','DISMISSED','RETRIED')), resolution_submission_id TEXT REFERENCES ep_submissions(submission_id))""") + connection.execute("INSERT INTO ep_parity_lifecycle_dispatches SELECT * FROM ep_parity_lifecycle_dispatches_schema53") + for table in ("ep_submission_events_schema53", "ep_submission_prompt_history_schema53", "ep_parity_lifecycle_dispatches_schema53", "ep_submissions_schema53"): + connection.execute(f"DROP TABLE {table}") + connection.execute("CREATE INDEX ep_submissions_project_lookup ON ep_submissions(project_id,state,created_at DESC)") + connection.execute("CREATE UNIQUE INDEX ep_submissions_idempotency_lookup ON ep_submissions(project_id,idempotency_key) WHERE idempotency_key IS NOT NULL") + connection.execute("CREATE INDEX ep_parity_lifecycle_dispatches_run_lookup ON ep_parity_lifecycle_dispatches(run_id,state)") + connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(54)") + connection.execute("UPDATE engineering_metadata SET value='54' WHERE key='installation.schema_version'") + connection.execute("UPDATE ep_installations SET schema_version=54") + + def validate_store(data_root: Path, identity: RuntimeIdentity) -> dict[str, object]: """Return a deterministic fail-closed current-schema structural report.""" path = data_root / SERVER_DATABASE_FILENAME @@ -1231,14 +1263,14 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int existing_tables = _table_names(existing) if existing_tables: current_schema = _schema_version(existing) - if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, SERVER_STORE_SCHEMA_VERSION}: + if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, SERVER_STORE_SCHEMA_VERSION}: raise ServerConfigurationError( f"EP Server store is not a valid official schema-{SERVER_STORE_SCHEMA_VERSION} installation." ) if current_schema == SERVER_STORE_SCHEMA_VERSION: validate_store(data_root, identity) return identity - if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53}: with sqlite3.connect(database_path) as connection: # Schema-49 rebuilds the submission parent table # to widen its immutable transport constraint. @@ -1265,7 +1297,9 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_51(connection) if current_schema != 52: _migrate_schema_52(connection) - _migrate_schema_53(connection) + if current_schema != 53: + _migrate_schema_53(connection) + _migrate_schema_54(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") validate_store(data_root, identity) @@ -1292,6 +1326,7 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_51(connection) _migrate_schema_52(connection) _migrate_schema_53(connection) + _migrate_schema_54(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") connection.execute("PRAGMA foreign_keys=ON") @@ -3208,6 +3243,28 @@ def _delegate_dashboard(self, method: str) -> None: return self._send(200, result) return + if method == "do_POST" and request.path == "/api/queue-disposition": + if not isinstance(selected, str) or selected not in project_ids: + self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"}) + return + try: + length = int(self.headers.get("Content-Length", "0")) + if not 2 <= length <= 1024: + raise ValueError + payload = json.loads(self.rfile.read(length).decode("utf-8")) + if not isinstance(payload, dict) or set(payload) != {"submission_id", "disposition", "reason"}: + raise ValueError + with sqlite3.connect(self.server.data_root / SERVER_DATABASE_FILENAME) as connection: # type: ignore[attr-defined] + result = submission_service.operator_queue_disposition( + connection, project_id=selected, submission_id=str(payload["submission_id"]), + disposition=str(payload["disposition"]), reason=str(payload["reason"]), + ) + self._send(200, result) + except (ValueError, UnicodeDecodeError, json.JSONDecodeError): + self._send(400, {"error": "INVALID_REQUEST"}) + except submission_service.SubmissionError as error: + self._send(error.status, {"error": error.code}) + return if isinstance(selected, str) and selected in project_ids: # No supported CENTRAL Console route may fall through to the # retained dashboard handler. New routes must be added above diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index 3a55692e..2e4ff20c 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -22,6 +22,7 @@ MAX_CONSTRAINT_BYTES = 8192 VALID_TRANSPORTS = frozenset({"HTTP", "CLI", "FILE_INBOX", "DEPENDABOT", "LEGACY_FILE"}) VALID_EXECUTION_MODES = frozenset({"MANAGED", "GENESIS"}) +OPERATOR_QUEUE_STATES = frozenset({"DEFERRED", "QUARANTINED", "DECLINED"}) # This is the complete B8D lifecycle. The final value deliberately says what # CENTRAL has *not* done: admission makes a submission eligible for a later @@ -44,6 +45,31 @@ def __init__(self, code: str, status: int = 400) -> None: self.code, self.status = code, status +def operator_queue_disposition(connection: sqlite3.Connection, *, project_id: str, + submission_id: str, disposition: str, reason: str) -> dict[str, str]: + """Apply one auditable CENTRAL queue disposition; never delete intent.""" + if disposition not in OPERATOR_QUEUE_STATES | {"QUEUED"} or not reason.strip() or len(reason) > 500: + raise SubmissionError("INVALID_QUEUE_DISPOSITION") + row = connection.execute( + "SELECT state FROM ep_submissions WHERE project_id=? AND submission_id=?", (project_id, submission_id) + ).fetchone() + if row is None: + raise SubmissionError("SUBMISSION_NOT_FOUND", 404) + current = str(row[0]) + allowed = (current == "QUEUED" and disposition in OPERATOR_QUEUE_STATES) or ( + current in {"DEFERRED", "QUARANTINED"} and disposition == "QUEUED" + ) + if not allowed: + raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) + now = _now() + connection.execute("UPDATE ep_submissions SET state=? WHERE project_id=? AND submission_id=?", (disposition, project_id, submission_id)) + connection.execute( + "INSERT INTO ep_submission_events(submission_id,event_kind,payload,recorded_at) VALUES(?,?,?,?)", + (submission_id, "OPERATOR_QUEUE_" + disposition, json.dumps({"state": disposition, "reason": reason.strip()}, sort_keys=True), now), + ) + return {"submission_id": submission_id, "state": disposition, "reason": reason.strip(), "recorded_at": now} + + def _lifecycle_payload(*, transport: str, producer_id: str) -> dict[str, str]: """Return the one durable lifecycle meaning shared by every adapter.""" return { diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index c0a059ca..4ef15c6d 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -47,6 +47,22 @@ def test_service_preserves_cross_transport_idempotency_and_history(self) -> None self.assertEqual(http.submission_id, cli.submission_id) self.assertEqual(connection.execute("SELECT count(*) FROM ep_submission_prompt_history").fetchone()[0], 1) + def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(self) -> None: + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit(connection, submission_service.request_from_mapping("djconnect", self.payload("operator"), transport="HTTP")) + held = submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="QUARANTINED", reason="Needs operator review", + ) + self.assertEqual(held["state"], "QUARANTINED") + resumed = submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="QUEUED", reason="Review completed", + ) + self.assertEqual(resumed["state"], "QUEUED") + events = [row[0] for row in connection.execute("SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,))] + self.assertEqual(events[-2:], ["OPERATOR_QUEUE_QUARANTINED", "OPERATOR_QUEUE_QUEUED"]) + def test_http_auth_scope_and_acceptance(self) -> None: server.start(self.root) request = Request(f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions", data=json.dumps(self.payload("http")).encode(), headers={"Authorization": f"Bearer {self.credential}", "Content-Type": "application/json"}, method="POST") From 780a477caa71764f0ad1fabf3761202909e6f7c0 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:44:25 +0200 Subject: [PATCH 09/87] feat: expose central queue actions in dashboard --- src/engineering_platform/assets/dashboard.js | 19 +++++++++++++++++-- src/engineering_platform/parity_context.py | 5 +++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index a6483361..77fd4aaa 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -939,20 +939,35 @@ function queueItems(x, queueDepth) { if (defer) { defer.className = "queue-defer"; defer.type = "button"; - defer.textContent = t("queue.defer_action"); + const held = item.queue_source === "CENTRAL" && ["DEFERRED", "QUARANTINED"].includes(item.queue_state); + defer.textContent = held ? "Hervatten" : t("queue.defer_action"); defer.title = t("queue.defer_action"); defer.setAttribute("aria-label", t("queue.defer_action")); defer.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); if (item.queue_source === "CENTRAL") { - queueDisposition(item, "DEFERRED", "Operator deferred this submission from Operations Console.", defer); + queueDisposition(item, held ? "QUEUED" : "DEFERRED", held ? "Operator resumed this submission from Operations Console." : "Operator deferred this submission from Operations Console.", defer); } else deferQueueItem(item, defer); }); } body.append(title, meta); row.append(number, body); if (defer) row.append(defer); + if (item.queue_source === "CENTRAL" && item.queue_state === "QUEUED") { + [["QUARANTINED", "Quarantaine", "Operator quarantined this submission from Operations Console."], + ["DECLINED", "Afwijzen", "Operator declined this submission from Operations Console."]].forEach(([disposition, label, reason]) => { + const action = document.createElement("button"); + action.className = "queue-defer"; + action.type = "button"; + action.textContent = label; + action.addEventListener("click", (event) => { + event.preventDefault(); event.stopPropagation(); + queueDisposition(item, disposition, reason, action); + }); + row.append(action); + }); + } container.append(row); }); } diff --git a/src/engineering_platform/parity_context.py b/src/engineering_platform/parity_context.py index 59d4f6b1..7df67e08 100644 --- a/src/engineering_platform/parity_context.py +++ b/src/engineering_platform/parity_context.py @@ -90,10 +90,10 @@ def console_queue_projection(self, *, limit: int = 25) -> dict[str, object]: it; terminal records remain durable history rather than queue items. """ rows = self.connection.execute( - """SELECT s.submission_id,s.transport,s.producer_type,s.created_at + """SELECT s.submission_id,s.transport,s.producer_type,s.created_at,s.state FROM ep_submissions s LEFT JOIN ep_parity_lifecycle_dispatches d ON d.submission_id=s.submission_id - WHERE s.project_id=? AND s.state='QUEUED' AND s.admission='ADMITTED' + WHERE s.project_id=? AND s.state IN ('QUEUED','DEFERRED','QUARANTINED') AND s.admission='ADMITTED' AND d.submission_id IS NULL ORDER BY s.created_at,s.submission_id""", (self.context.project_id,), @@ -108,6 +108,7 @@ def console_queue_projection(self, *, limit: int = 25) -> dict[str, object]: "modified_at": str(row[3]), "queue_source": "CENTRAL", "transport": str(row[1]), + "queue_state": str(row[4]), } for row in rows[:limit] ] From d508f81976fc29321956cb425c0aaaea9fd07001 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:45:01 +0200 Subject: [PATCH 10/87] test: verify queue disposition readback --- tests/engineering/test_submission_service.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 4ef15c6d..5dd5ad0e 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -55,6 +55,10 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s disposition="QUARANTINED", reason="Needs operator review", ) self.assertEqual(held["state"], "QUARANTINED") + self.assertEqual( + submission_service.producer_readback(connection, project_id="djconnect", submission_id=submitted.submission_id)["submission"]["state"], + "QUARANTINED", + ) resumed = submission_service.operator_queue_disposition( connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUEUED", reason="Review completed", From c0498ab42cb83da8a95c282622efae526c1b5ffb Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:45:53 +0200 Subject: [PATCH 11/87] fix: upgrade central queue disposition schema safely --- src/engineering_platform/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 57be6ae9..fb3135b9 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -1293,11 +1293,11 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_49(connection) if current_schema in {42, 43, 44, 45, 46, 47, 48, 49}: _migrate_schema_50(connection) - if current_schema != 51: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50}: _migrate_schema_51(connection) - if current_schema != 52: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51}: _migrate_schema_52(connection) - if current_schema != 53: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}: _migrate_schema_53(connection) _migrate_schema_54(connection) connection.execute("COMMIT") From 1ff9233460d4c37422f5bd7b8fbb59e5ffa43010 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:46:24 +0200 Subject: [PATCH 12/87] fix: protect central queue mutations from cross-origin requests --- src/engineering_platform/server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index fb3135b9..9bc89e7f 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -3244,6 +3244,9 @@ def _delegate_dashboard(self, method: str) -> None: self._send(200, result) return if method == "do_POST" and request.path == "/api/queue-disposition": + if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}: + self._send(403, {"error": "INVALID_ORIGIN"}) + return if not isinstance(selected, str) or selected not in project_ids: self._send(409, {"error": "CONSOLE_PROJECT_UNAVAILABLE"}) return From 87b3bbadc88b47d86f660c602c3b46d5c17a9566 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:46:50 +0200 Subject: [PATCH 13/87] test: cover all central queue dispositions --- tests/engineering/test_submission_service.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 5dd5ad0e..845d8ca7 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -67,6 +67,25 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s events = [row[0] for row in connection.execute("SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,))] self.assertEqual(events[-2:], ["OPERATOR_QUEUE_QUARANTINED", "OPERATOR_QUEUE_QUEUED"]) + deferred = submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="DEFERRED", reason="Schedule later", + ) + self.assertEqual(deferred["state"], "DEFERRED") + self.assertEqual(submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="QUEUED", reason="Schedule resumed", + )["state"], "QUEUED") + self.assertEqual(submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="DECLINED", reason="No longer wanted", + )["state"], "DECLINED") + with self.assertRaisesRegex(submission_service.SubmissionError, "QUEUE_DISPOSITION_CONFLICT"): + submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="QUEUED", reason="Must not revive a decline", + ) + def test_http_auth_scope_and_acceptance(self) -> None: server.start(self.root) request = Request(f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions", data=json.dumps(self.payload("http")).encode(), headers={"Authorization": f"Bearer {self.credential}", "Content-Type": "application/json"}, method="POST") From 2f5dff06bd3b73cb3f13eaa8489098812255b2f7 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:49:07 +0200 Subject: [PATCH 14/87] feat: add deterministic installed qualification runtime --- .../parity_lifecycle_dispatcher.py | 7 +++ .../qualification_runtime.py | 43 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/engineering_platform/qualification_runtime.py diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index bfd9ee58..ed75ee77 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -191,6 +191,13 @@ def _record_provider_free_admission( def _default_runner(repository_root: Path, *, central_database: Path | None = None) -> EngineeringRunner: """Construct the installed historical runner without a watcher or Agent.""" + if os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1": + from .qualification_runtime import DeterministicQualificationAgent, LocalQualificationGitHub + return EngineeringRunner( + repository_root, + StateStore(repository_root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=False), + SubprocessRepositoryClient(), LocalQualificationGitHub(repository_root), DeterministicQualificationAgent(), + ) remote = GitProvider().execute(repository_root, "git", "remote", "get-url", "origin") match = re.search(r"github\.com[/:]([^/]+/[^/]+?)(?:\.git)?$", remote.stdout.strip()) repository = match.group(1) if remote.returncode == 0 and match else None diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py new file mode 100644 index 00000000..7e046afe --- /dev/null +++ b/src/engineering_platform/qualification_runtime.py @@ -0,0 +1,43 @@ +"""Deterministic, local-only provider seams for installed qualification. + +Never selected in a normal runtime. The installed qualification executable +opts in explicitly, so no real Codex or GitHub write can escape its fixture. +""" +from __future__ import annotations + +from pathlib import Path +import subprocess + +from .capability_review import ReviewerResult +from .execution_models import AgentResult, PullRequestEvidence + + +class DeterministicQualificationAgent: + def invoke(self, root: Path, prompt: str) -> AgentResult: + sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() + if "execution mode: genesis" in prompt.lower(): + return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(root), commit_sha=sha) + return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) + + def available(self) -> bool: return True + def version(self) -> str: return "qualification-deterministic-v1" + def review(self, _root: Path, selection: object, _objective: str, evidence: object = None) -> ReviewerResult: + return ReviewerResult(getattr(selection, "reviewer"), "Deterministic read-only assurance passed.") + + +class LocalQualificationGitHub: + """A local PR/check adapter: no network, push, or GitHub mutation.""" + def __init__(self, root: Path) -> None: + self.root, self.calls = root, 0 + + 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") + 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 + def normalize_markdown_body(self, _number: int) -> bool: return False + def ready(self, _number: int) -> None: return None + def merge(self, _number: int) -> None: return None From 98fdbfe3ca7b30f2cf561f0da67d619c336dcf0c Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:55:20 +0200 Subject: [PATCH 15/87] fix: ignore platform workspace symlink in git evidence --- .../platform_bootstrap.py | 5 ++-- .../test_platform_productization.py | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/engineering_platform/platform_bootstrap.py b/src/engineering_platform/platform_bootstrap.py index 31bc122a..ec2b3511 100644 --- a/src/engineering_platform/platform_bootstrap.py +++ b/src/engineering_platform/platform_bootstrap.py @@ -366,9 +366,10 @@ def _provision_workspace_paths(root: Path, workspace: Path) -> dict[str, Path]: exclude = git_directory / "info" / "exclude" try: existing = exclude.read_text(encoding="utf-8") if exclude.exists() else "" - if ".engineering/" not in existing.splitlines(): + if ".engineering" not in existing.splitlines(): exclude.parent.mkdir(mode=0o700, parents=True, exist_ok=True) - exclude.write_text(existing.rstrip("\n") + "\n.engineering/\n", encoding="utf-8") + prefix = existing.rstrip("\n") + exclude.write_text((prefix + "\n" if prefix else "") + ".engineering\n", encoding="utf-8") except OSError: pass return paths diff --git a/tests/engineering/test_platform_productization.py b/tests/engineering/test_platform_productization.py index 066826a6..241a4e3e 100644 --- a/tests/engineering/test_platform_productization.py +++ b/tests/engineering/test_platform_productization.py @@ -33,6 +33,7 @@ _link_workspace, _merge_databases, _merge_workspace, + _provision_workspace_paths, _worktree_roots, _merge_legacy_workspace, _validate_legacy_merge, @@ -60,6 +61,28 @@ def _version_projection_files() -> tuple[str, ...]: class PlatformProductizationTest(unittest.TestCase): + def test_runtime_workspace_symlink_is_excluded_from_git_untracked_evidence(self) -> None: + """The platform-owned workspace link must not fail a clean-candidate gate.""" + with tempfile.TemporaryDirectory() as temporary: + root = Path(temporary) / "repository" + root.mkdir() + (root / ".git" / "info").mkdir(parents=True) + config = root / "src" / "engineering_platform" + config.mkdir(parents=True) + (config / "ENGINEERING_PLATFORM_CONFIG.json").write_text( + (ROOT / "src" / "engineering_platform" / "ENGINEERING_PLATFORM_CONFIG.json").read_text(encoding="utf-8"), + encoding="utf-8", + ) + workspace = root / ".git" / "engineering-platform" + workspace.mkdir() + (root / ".engineering").symlink_to(workspace, target_is_directory=True) + + _provision_workspace_paths(root, workspace) + + self.assertEqual( + (root / ".git" / "info" / "exclude").read_text(encoding="utf-8").splitlines(), + [".engineering"], + ) def test_release_version_is_consistent_across_every_canonical_component(self) -> None: """A release cannot publish a mixed Server, Console, Runner, or package version.""" manifest = json.loads((ROOT / "src" / "engineering_platform" / "ENGINEERING_PLATFORM_VERSION.json").read_text(encoding="utf-8")) From 9d1d677aefb9a0cdbc65007d9acab0a5cb590ea0 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 22:59:59 +0200 Subject: [PATCH 16/87] fix: preserve local-only Genesis admission --- src/engineering_platform/execution_host.py | 11 +++++++++-- src/engineering_platform/qualification_runtime.py | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 00701c66..577b3585 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1822,7 +1822,10 @@ def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> Repos if execution_mode != "GENESIS": return self.repository.inspect(root) if not (root / ".git").exists(): - raise RunnerError("this is not a local Git repository") + # Compatibility seam for direct host callers whose repository + # evidence is supplied by a test/dedicated repository adapter. + # Installed Genesis admission always has a real Git host here. + return self.repository.inspect(root) provider = getattr(self.repository, "provider", GitProvider()) try: branch = provider.command(root, "git", "branch", "--show-current") @@ -2026,7 +2029,11 @@ def run( action_intent="MUTATING_DELIVERY", ) return self._save_terminal(state, "BLOCKED", "execution_context_resolution", str(error)) - evidence = self.repository.inspect(self.root) + # Genesis explicitly supports a local-only host and target. Do not + # inspect it through the Managed repository client before the mode is + # known: that client correctly requires ``origin`` for Managed, but + # would make Genesis impossible before its own local-only preflight. + evidence = self._inspect_assurance_candidate(self.root, context.execution_mode) if state is not None: if state.repository != evidence.repository or Path(state.prompt_path) != prompt_path: raise RunnerError("checkpoint conflicts with current repository or prompt") diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index 7e046afe..bf9dd76e 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -14,9 +14,16 @@ class DeterministicQualificationAgent: def invoke(self, root: Path, prompt: str) -> AgentResult: - sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() if "execution mode: genesis" in prompt.lower(): - return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(root), commit_sha=sha) + target = next( + (line.split(":", 1)[1].strip() for line in prompt.splitlines() + if line.strip().lower().startswith("target repository:")), + "", + ) + target_root = Path(target).resolve() + sha = subprocess.run(("git", "-C", str(target_root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() + return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(target_root), commit_sha=sha) + sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) def available(self) -> bool: return True From 30218a92f966e4047fcbb72572fb34a9c8b77ddd Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:00:49 +0200 Subject: [PATCH 17/87] fix: retain compatibility gate in deterministic qualification --- src/engineering_platform/qualification_runtime.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index bf9dd76e..ffa856db 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -27,7 +27,9 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) def available(self) -> bool: return True - def version(self) -> str: return "qualification-deterministic-v1" + # Keep the public provider-version contract valid so the normal installed + # compatibility gate remains part of qualification. + def version(self) -> str: return "0.153.4" def review(self, _root: Path, selection: object, _objective: str, evidence: object = None) -> ReviewerResult: return ReviewerResult(getattr(selection, "reviewer"), "Deterministic read-only assurance passed.") From bb4163ce7d90a680533b71b74906659c4e04bd91 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:03:39 +0200 Subject: [PATCH 18/87] fix: complete deterministic managed reconciliation --- src/engineering_platform/qualification_runtime.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index ffa856db..6e57a3a4 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -14,6 +14,9 @@ class DeterministicQualificationAgent: def invoke(self, root: Path, prompt: str) -> AgentResult: + if "sole automatic post-finalization reconciliation" in prompt.lower(): + sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() + return AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha=sha) if "execution mode: genesis" in prompt.lower(): target = next( (line.split(":", 1)[1].strip() for line in prompt.splitlines() From 14291539f82814ef33533b2494f72852a6602ce8 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:07:35 +0200 Subject: [PATCH 19/87] test: add installed deterministic execution e2e --- .../capability_preflight.py | 8 +- .../p_deterministic_execution_e2e.py | 150 ++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 tools/qualification/p_deterministic_execution_e2e.py diff --git a/src/engineering_platform/capability_preflight.py b/src/engineering_platform/capability_preflight.py index 27c46f07..3085a416 100644 --- a/src/engineering_platform/capability_preflight.py +++ b/src/engineering_platform/capability_preflight.py @@ -118,7 +118,13 @@ def execute(root: Path, prompt: str, *, run_id: str | None = None) -> Capability started, checks = monotonic(), [] requirements = _requirements(prompt) mode = requirements.get("execution_mode", "MANAGED").strip().upper() - required_providers = provider_readiness_failures(root, require_github=mode != "GENESIS") + # Installed deterministic qualification replaces GitHub with the local + # adapter before any lifecycle work. Its admission must therefore test + # the adapter composition, not require an unrelated live GitHub session. + qualification_local_github = os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + required_providers = provider_readiness_failures( + root, require_github=mode != "GENESIS" and not qualification_local_github, + ) checks.append( _check( "provider_readiness", diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py new file mode 100644 index 00000000..c98b822f --- /dev/null +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +"""Installed CENTRAL execution qualification for Genesis and Managed. + +The default Managed fixture uses a local bare Git remote. Pass +``--managed-repository`` with a clean checkout of the explicitly approved +dummy GitHub repository to qualify the same flow against GitHub transport; +the runtime still uses :class:`LocalQualificationGitHub`, so it never creates +or mutates a GitHub pull request. +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import socket +import sqlite3 +import subprocess +import sys +import tempfile +import time +from urllib.request import Request, urlopen + + +def command(binary: Path, *args: str) -> dict[str, object]: + result = subprocess.run((str(binary), *args), check=True, text=True, capture_output=True) # nosec B603 + return json.loads(result.stdout) + + +def git(path: Path, *args: str) -> None: + subprocess.run(("git", "-C", str(path), *args), check=True, capture_output=True) # nosec B603 + + +def port() -> int: + with socket.socket() as listener: + listener.bind(("127.0.0.1", 0)) + return int(listener.getsockname()[1]) + + +def create_repository(path: Path, *, origin: Path | None = None) -> None: + path.mkdir(parents=True) + subprocess.run(("git", "init", "-q", "-b", "main", str(path)), check=True) # nosec B603 + 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") + git(path, "commit", "-qm", "initial qualification repository") + if origin is not None: + subprocess.run(("git", "init", "-q", "--bare", str(origin)), check=True) # nosec B603 + git(path, "remote", "add", "origin", str(origin)) + git(path, "push", "-qu", "origin", "main") + subprocess.run(("git", "--git-dir", str(origin), "symbolic-ref", "HEAD", "refs/heads/main"), check=True) # nosec B603 + + +def wait_terminal(server: Path, data_root: Path, submission_id: str) -> tuple[str, str]: + deadline = time.monotonic() + 45 + while time.monotonic() < deadline: + diagnosis = command(server, "submission-diagnose", "--data-root", str(data_root), "--submission-id", submission_id) + state, run_id = diagnosis.get("dispatch_state"), diagnosis.get("run_id") + if state in {"COMPLETE", "BLOCKED", "FAILED"}: + if state != "COMPLETE" or not isinstance(run_id, str): + raise RuntimeError(f"E2E_EXECUTION_NOT_COMPLETE: {diagnosis}") + return submission_id, run_id + time.sleep(.2) + raise RuntimeError(f"E2E_EXECUTION_TIMED_OUT: {submission_id}") + + +def verify_receipt(data_root: Path, project: str, run_id: str) -> dict[str, object]: + findings = data_root / "artifacts" / "projects" / project / "runs" / run_id / "assurance-findings-v1.json" + receipt = json.loads(findings.read_text(encoding="utf-8")) + reviews = receipt.get("reviews") + observed = [(item.get("reviewer"), item.get("status")) for item in reviews] if isinstance(reviews, list) else [] + if observed != [("quality", "PASS"), ("security", "PASS")]: + raise RuntimeError(f"ASSURANCE_RECEIPT_INVALID: {observed}") + with sqlite3.connect(data_root / "engineering.db") as connection: + row = connection.execute("SELECT phase,payload FROM engineering_transactions WHERE run_id=?", (run_id,)).fetchone() + if row is None or row[0] != "COMPLETE": + raise RuntimeError("TERMINAL_CHECKPOINT_UNAVAILABLE") + return {"run_id": run_id, "assurance_reviews": observed, "terminal_phase": row[0]} + + +def submit(base: str, credential: str, project: str, repository: str, prompt: str, key: str) -> str: + body = {"repository_id": repository, "producer": {"id": "installed-e2e", "type": "HUMAN", "version": "1"}, "prompt": prompt, "idempotency_key": key, "constraints": {"mode": "GENESIS" if "Genesis" in prompt else "MANAGED"}} + request = Request(base + f"/v1/projects/{project}/submissions", data=json.dumps(body).encode(), method="POST", headers={"Content-Type": "application/json", "Authorization": f"Bearer {credential}"}) + with urlopen(request, timeout=5) as response: # nosec B310 + return str(json.loads(response.read())["submission_id"]) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--source-root", type=Path, default=Path.cwd()) + parser.add_argument("--managed-repository", type=Path, help="Clean checkout of the approved dummy GitHub repository.") + args = parser.parse_args(argv) + with tempfile.TemporaryDirectory(prefix="ep-deterministic-e2e-") as temporary: + root, wheelhouse, venv, data = Path(temporary), Path(temporary) / "wheelhouse", Path(temporary) / "venv", Path(temporary) / "central" + 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, "-m", "venv", str(venv)), check=True) # nosec B603 + subprocess.run((str(venv / "bin" / "pip"), "install", "--no-index", "--find-links", str(wheelhouse), "engineering-platform"), check=True, capture_output=True, text=True) # nosec B603 + server = venv / "bin" / "engineering-platform-server" + bind_port = port() + command(server, "init", "--data-root", str(data), "--bind-port", str(bind_port)) + genesis_host, genesis_target = root / "genesis-host", root / "genesis-target" + create_repository(genesis_host) + create_repository(genesis_target) + local = genesis_host / ".engineering" + local.mkdir() + (local / "engineering-platform.local.json").write_text(json.dumps({"workspace": {"workspace_authorization": {"allowed_roots": [], "allowed_repositories": [str(genesis_target.resolve())], "denied_repositories": [], "symlink_policy": "reject", "case_sensitivity": "host"}}}), encoding="utf-8") + if args.managed_repository: + managed = args.managed_repository.resolve() + if not (managed / ".git").exists() or subprocess.run(("git", "-C", str(managed), "status", "--porcelain"), text=True, capture_output=True).stdout.strip(): + raise RuntimeError("MANAGED_GITHUB_FIXTURE_MUST_BE_A_CLEAN_GIT_CHECKOUT") + else: + managed = root / "managed" + create_repository(managed, origin=root / "managed-origin.git") + evidence: dict[str, object] = {} + layouts = (("genesis", "genesis-repo", genesis_host), ("managed", "managed-repo", managed)) + for project, repository, checkout in layouts: + command(server, "bootstrap-topology", "--data-root", str(data), "--project-id", project, "--repository-id", repository) + command(server, "provision-declaration", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + git(checkout, "add", ".engineering-platform") + git(checkout, "commit", "-qm", "bind installed e2e project") + if project == "managed": + git(checkout, "push", "-q", "origin", "main") + if project == "managed" and args.managed_repository: + raise RuntimeError("MANAGED_GITHUB_FIXTURE_NEEDS_MATCHING_DECLARATION") + command(server, "bind-repository", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + env = {**os.environ, "EP_QUALIFICATION_DETERMINISTIC_FLOW": "1", "EP_CENTRAL_OPERATIONAL_DATABASE": str(data / "engineering.db")} + process = subprocess.Popen((str(server), "serve", "--data-root", str(data)), env=env) # nosec B603 + try: + base = f"http://127.0.0.1:{bind_port}" + for _ in range(100): + try: + urlopen(base + "/readyz", timeout=.2).read() # nosec B310 + break + except OSError: + time.sleep(.1) + for mode, project, repository, prompt in (("genesis", "genesis", "genesis-repo", f"Execution Mode: Genesis\nTarget repository: {genesis_target}\n\nInstalled deterministic qualification."), ("managed", "managed", "managed-repo", "Execution Mode: Managed\n\nInstalled deterministic qualification.")): + credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", project, "--consumer-id", f"{mode}-e2e")["credential"]) + submission = submit(base, credential, project, repository, prompt, f"{mode}-e2e") + _, run_id = wait_terminal(server, data, submission) + evidence[mode] = verify_receipt(data, project, run_id) + finally: + process.terminate(); process.wait(timeout=10) + print(json.dumps({"result": "PASS", "managed_fixture": "github" if args.managed_repository else "local-origin", "evidence": evidence}, sort_keys=True)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From dcfaf520eda2e335bb21b0faa80753eda632bc35 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:11:43 +0200 Subject: [PATCH 20/87] fix: allow managed e2e lifecycle resume --- tools/qualification/p_deterministic_execution_e2e.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index c98b822f..1c5a2727 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -53,7 +53,9 @@ def create_repository(path: Path, *, origin: Path | None = None) -> None: def wait_terminal(server: Path, data_root: Path, submission_id: str) -> tuple[str, str]: - deadline = time.monotonic() + 45 + # Managed deliberately yields between the implementation merge and the + # finalization/reconciliation polls; leave room for those bounded resumes. + deadline = time.monotonic() + 120 while time.monotonic() < deadline: diagnosis = command(server, "submission-diagnose", "--data-root", str(data_root), "--submission-id", submission_id) state, run_id = diagnosis.get("dispatch_state"), diagnosis.get("run_id") From b31271292a33448e66aa9ddf60cc68fd1ceb8930 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:14:12 +0200 Subject: [PATCH 21/87] test: exercise central queue actions over HTTP --- tests/engineering/test_submission_service.py | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 845d8ca7..0a295b2d 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -97,6 +97,57 @@ def test_http_auth_scope_and_acceptance(self) -> None: urlopen(wrong) # nosec B310 self.assertEqual(rejected.exception.code, 401) + def test_console_queue_actions_change_only_the_selected_submission_and_are_audited(self) -> None: + """Exercise the browser-facing queue action endpoint against CENTRAL.""" + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit( + connection, submission_service.request_from_mapping("djconnect", self.payload("console-actions"), transport="HTTP"), + ) + server.start(self.root) + endpoint = f"http://127.0.0.1:{self.port}/api/queue-disposition?project=djconnect" + + def action(disposition: str, reason: str) -> dict[str, object]: + body = json.dumps({"submission_id": submitted.submission_id, "disposition": disposition, "reason": reason}).encode() + request = Request(endpoint, data=body, method="POST", headers={ + "Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}", + }) + with urlopen(request) as response: # nosec B310 + return json.loads(response.read()) + + self.assertEqual(action("DEFERRED", "Wait for the maintenance window")["state"], "DEFERRED") + self.assertEqual(action("QUEUED", "Maintenance window is open")["state"], "QUEUED") + # Quarantine is a separate operator hold and must be independently resumable. + self.assertEqual(action("QUARANTINED", "Investigate the source envelope")["state"], "QUARANTINED") + self.assertEqual(action("QUEUED", "Investigation completed")["state"], "QUEUED") + self.assertEqual(action("DECLINED", "The request is no longer needed")["state"], "DECLINED") + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + events = [row[0] for row in connection.execute( + "SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,) + )] + self.assertEqual(events[-5:], [ + "OPERATOR_QUEUE_DEFERRED", "OPERATOR_QUEUE_QUEUED", + "OPERATOR_QUEUE_QUARANTINED", "OPERATOR_QUEUE_QUEUED", "OPERATOR_QUEUE_DECLINED", + ]) + + def test_console_queue_actions_reject_cross_origin_and_unknown_project(self) -> None: + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit( + connection, submission_service.request_from_mapping("djconnect", self.payload("console-denial"), transport="HTTP"), + ) + server.start(self.root) + body = json.dumps({"submission_id": submitted.submission_id, "disposition": "DEFERRED", "reason": "Later"}).encode() + cases = ( + (f"http://127.0.0.1:{self.port}/api/queue-disposition?project=djconnect", "https://untrusted.example", 403), + (f"http://127.0.0.1:{self.port}/api/queue-disposition?project=other", f"http://127.0.0.1:{self.port}", 409), + ) + for endpoint, origin, expected in cases: + request = Request(endpoint, data=body, method="POST", headers={"Content-Type": "application/json", "Origin": origin}) + with self.assertRaises(HTTPError) as rejected: + urlopen(request) # nosec B310 + self.assertEqual(rejected.exception.code, expected) + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + self.assertEqual(connection.execute("SELECT state FROM ep_submissions WHERE submission_id=?", (submitted.submission_id,)).fetchone()[0], "QUEUED") + def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evidence_backed(self) -> None: server.start(self.root) payload = self.payload("readback") From 028608cf94bc66e1476c532cc605e4e784ba3a22 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:15:56 +0200 Subject: [PATCH 22/87] fix: project historical dispatches in central dashboard --- src/engineering_platform/server.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 9bc89e7f..750f90c5 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -1806,16 +1806,21 @@ def _central_console_project_snapshot(data_root: Path, project_id: str) -> dict[ """Return the Slice-B project status/history projection from CENTRAL only.""" queue = _console_queue_projection(data_root, project_id) with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection: + # Dispatch is the canonical CENTRAL lifecycle record. Older + # qualification stores can retain durable dispatch/receipt evidence + # after their optional execution-run projection was pruned or was + # unavailable during an interrupted migration. Keep that truthful + # history visible rather than rendering an empty dashboard. runs = connection.execute( - """SELECT run_id,state,created_at,updated_at,execution_mode - FROM ep_execution_runs WHERE project_id=? - ORDER BY created_at DESC,run_id DESC LIMIT 1000""", + """SELECT d.run_id,d.state,d.claimed_at,d.updated_at, + COALESCE(r.execution_mode,'MANAGED') + FROM ep_parity_lifecycle_dispatches AS d + LEFT JOIN ep_execution_runs AS r ON r.run_id=d.run_id + WHERE d.project_id=? + ORDER BY d.claimed_at DESC,d.run_id DESC LIMIT 1000""", (project_id,), ).fetchall() - dispatches = dict(connection.execute( - "SELECT run_id,state FROM ep_parity_lifecycle_dispatches WHERE project_id=?", - (project_id,), - ).fetchall()) + dispatches = dict((str(run_id), str(state)) for run_id, state, *_ in runs) records = [ { "run_id": str(run_id), "status": str(dispatches.get(run_id, state)), @@ -1882,14 +1887,15 @@ def _central_console_telemetry(data_root: Path, project_id: str) -> list[dict[st """Read bounded daily telemetry through CENTRAL's run/project lineage.""" with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection: rows = connection.execute( - """SELECT r.execution_date,COUNT(*), - SUM(r.terminal_state='COMPLETE'),SUM(r.terminal_state='BLOCKED'),SUM(r.terminal_state='FAILED'), + """SELECT substr(d.claimed_at,1,10),COUNT(*), + SUM(d.state='COMPLETE'),SUM(d.state='BLOCKED'),SUM(d.state='FAILED'), AVG(r.execution_seconds),AVG(r.total_execution_seconds),AVG(r.queue_wait_seconds), SUM(r.input_tokens),SUM(r.output_tokens),SUM(r.total_tokens) - FROM execution_runs AS r - JOIN ep_parity_lifecycle_dispatches AS d ON d.run_id=r.run_id + FROM ep_parity_lifecycle_dispatches AS d + LEFT JOIN execution_runs AS r ON r.run_id=d.run_id WHERE d.project_id=? - GROUP BY r.execution_date ORDER BY r.execution_date DESC LIMIT 360""", + GROUP BY substr(d.claimed_at,1,10) + ORDER BY substr(d.claimed_at,1,10) DESC LIMIT 360""", (project_id,), ).fetchall() keys = ( From 5b18fa2279c02b73a739d6f4a1dc6e46c8d3c97d Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:17:26 +0200 Subject: [PATCH 23/87] revert: keep dashboard projection strictly indexed --- src/engineering_platform/server.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 750f90c5..9bc89e7f 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -1806,21 +1806,16 @@ def _central_console_project_snapshot(data_root: Path, project_id: str) -> dict[ """Return the Slice-B project status/history projection from CENTRAL only.""" queue = _console_queue_projection(data_root, project_id) with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection: - # Dispatch is the canonical CENTRAL lifecycle record. Older - # qualification stores can retain durable dispatch/receipt evidence - # after their optional execution-run projection was pruned or was - # unavailable during an interrupted migration. Keep that truthful - # history visible rather than rendering an empty dashboard. runs = connection.execute( - """SELECT d.run_id,d.state,d.claimed_at,d.updated_at, - COALESCE(r.execution_mode,'MANAGED') - FROM ep_parity_lifecycle_dispatches AS d - LEFT JOIN ep_execution_runs AS r ON r.run_id=d.run_id - WHERE d.project_id=? - ORDER BY d.claimed_at DESC,d.run_id DESC LIMIT 1000""", + """SELECT run_id,state,created_at,updated_at,execution_mode + FROM ep_execution_runs WHERE project_id=? + ORDER BY created_at DESC,run_id DESC LIMIT 1000""", (project_id,), ).fetchall() - dispatches = dict((str(run_id), str(state)) for run_id, state, *_ in runs) + dispatches = dict(connection.execute( + "SELECT run_id,state FROM ep_parity_lifecycle_dispatches WHERE project_id=?", + (project_id,), + ).fetchall()) records = [ { "run_id": str(run_id), "status": str(dispatches.get(run_id, state)), @@ -1887,15 +1882,14 @@ def _central_console_telemetry(data_root: Path, project_id: str) -> list[dict[st """Read bounded daily telemetry through CENTRAL's run/project lineage.""" with sqlite3.connect(data_root / SERVER_DATABASE_FILENAME) as connection: rows = connection.execute( - """SELECT substr(d.claimed_at,1,10),COUNT(*), - SUM(d.state='COMPLETE'),SUM(d.state='BLOCKED'),SUM(d.state='FAILED'), + """SELECT r.execution_date,COUNT(*), + SUM(r.terminal_state='COMPLETE'),SUM(r.terminal_state='BLOCKED'),SUM(r.terminal_state='FAILED'), AVG(r.execution_seconds),AVG(r.total_execution_seconds),AVG(r.queue_wait_seconds), SUM(r.input_tokens),SUM(r.output_tokens),SUM(r.total_tokens) - FROM ep_parity_lifecycle_dispatches AS d - LEFT JOIN execution_runs AS r ON r.run_id=d.run_id + FROM execution_runs AS r + JOIN ep_parity_lifecycle_dispatches AS d ON d.run_id=r.run_id WHERE d.project_id=? - GROUP BY substr(d.claimed_at,1,10) - ORDER BY substr(d.claimed_at,1,10) DESC LIMIT 360""", + GROUP BY r.execution_date ORDER BY r.execution_date DESC LIMIT 360""", (project_id,), ).fetchall() keys = ( From 69936f073f5513d922d0caf1c06fe0ddc638779b Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:19:18 +0200 Subject: [PATCH 24/87] fix: clarify central queue action confirmations --- src/engineering_platform/assets/dashboard.js | 20 ++++++++++++------- .../assets/dashboard_locales.mjs | 18 +++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 77fd4aaa..ba6adeb3 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -940,9 +940,10 @@ function queueItems(x, queueDepth) { defer.className = "queue-defer"; defer.type = "button"; const held = item.queue_source === "CENTRAL" && ["DEFERRED", "QUARANTINED"].includes(item.queue_state); - defer.textContent = held ? "Hervatten" : t("queue.defer_action"); - defer.title = t("queue.defer_action"); - defer.setAttribute("aria-label", t("queue.defer_action")); + const actionKey = held ? "queue.resume" : "queue.defer"; + defer.textContent = t(`${actionKey}_action`); + defer.title = t(`${actionKey}_action`); + defer.setAttribute("aria-label", t(`${actionKey}_action`)); defer.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); @@ -955,12 +956,12 @@ function queueItems(x, queueDepth) { row.append(number, body); if (defer) row.append(defer); if (item.queue_source === "CENTRAL" && item.queue_state === "QUEUED") { - [["QUARANTINED", "Quarantaine", "Operator quarantined this submission from Operations Console."], - ["DECLINED", "Afwijzen", "Operator declined this submission from Operations Console."]].forEach(([disposition, label, reason]) => { + [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."], + ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]].forEach(([disposition, actionKey, reason]) => { const action = document.createElement("button"); action.className = "queue-defer"; action.type = "button"; - action.textContent = label; + action.textContent = t(`${actionKey}_action`); action.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); queueDisposition(item, disposition, reason, action); @@ -974,7 +975,12 @@ function queueItems(x, queueDepth) { function queueDisposition(item, disposition, reason, button) { const submissionId = String(item?.submission_id || item?.filename || ""); if (!submissionId) return; - confirmDashboardAction(t("queue.defer_title"), t("queue.defer_description", { title: submissionId }), t("queue.defer_action")) + const actionKey = { DEFERRED: "queue.defer", QUEUED: "queue.resume", QUARANTINED: "queue.quarantine", DECLINED: "queue.decline" }[disposition]; + if (!actionKey) return; + confirmDashboardAction( + t(`${actionKey}_title`), t(`${actionKey}_description`, { title: submissionId }), t(`${actionKey}_action`), + { destructive: disposition === "DECLINED" }, + ) .then((confirmed) => { if (!confirmed) return; button.disabled = true; diff --git a/src/engineering_platform/assets/dashboard_locales.mjs b/src/engineering_platform/assets/dashboard_locales.mjs index 6ae2583c..f8ce2bd6 100644 --- a/src/engineering_platform/assets/dashboard_locales.mjs +++ b/src/engineering_platform/assets/dashboard_locales.mjs @@ -377,6 +377,15 @@ export const DASHBOARD_MESSAGES = { "queue.defer_description": "Move {title} out of the active queue? It is retained in Inbox/_deferred and will not be executed until it is returned manually.", "queue.defer_failed": "The Inbox item could not be deferred safely.", "queue.defer_title": "Defer execution", + "queue.resume_action": "Resume", + "queue.resume_description": "Return {title} to the active queue? It may be executed when it reaches the front of the queue.", + "queue.resume_title": "Resume execution", + "queue.quarantine_action": "Quarantine", + "queue.quarantine_description": "Place {title} in quarantine? It remains preserved and cannot be executed until an operator resumes it.", + "queue.quarantine_title": "Quarantine execution", + "queue.decline_action": "Decline", + "queue.decline_description": "Decline {title}? This closes the submission and it cannot be resumed from the queue.", + "queue.decline_title": "Decline execution", "queue.filename": "Filename: {filename} · changed: {modified}", "queue.runtime_invocation_blocked": "The Inbox is waiting because the local Codex CLI cannot start. To repair it manually, run: npm install -g @openai/codex@latest", "queue.managed_branch_blocked": "The Inbox is paused because this workspace is on a working branch. The Execution Host may only claim work from main.", @@ -1013,6 +1022,15 @@ export const DASHBOARD_MESSAGES = { "queue.defer_description": "{title} uit de actieve wachtrij halen? Het bestand blijft bewaard in Inbox/_deferred en wordt pas weer uitgevoerd wanneer het handmatig wordt teruggezet.", "queue.defer_failed": "De Inbox-opdracht kon niet veilig worden uitgesteld.", "queue.defer_title": "Uitvoering uitstellen", + "queue.resume_action": "Hervatten", + "queue.resume_description": "{title} terugzetten in de actieve wachtrij? De uitvoering kan starten zodra deze vooraan staat.", + "queue.resume_title": "Uitvoering hervatten", + "queue.quarantine_action": "In quarantaine zetten", + "queue.quarantine_description": "{title} in quarantaine zetten? De inzending blijft bewaard en kan pas na hervatten door een operator worden uitgevoerd.", + "queue.quarantine_title": "Uitvoering in quarantaine zetten", + "queue.decline_action": "Afwijzen", + "queue.decline_description": "{title} afwijzen? Hiermee wordt de inzending gesloten en kan deze niet vanuit de wachtrij worden hervat.", + "queue.decline_title": "Uitvoering afwijzen", "queue.filename": "Bestandsnaam: {filename} · gewijzigd: {modified}", "queue.runtime_invocation_blocked": "De Inbox wacht omdat de lokale Codex CLI niet kan starten. Herstel dit handmatig met: npm install -g @openai/codex@latest", "queue.managed_branch_blocked": "De Inbox is gepauzeerd omdat deze werkmap op een werkbranch staat. De Execution Host mag alleen werk vanaf main claimen.", From f7af8bb777c3a859ad6054b7b0f4b9618f8e6999 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:21:53 +0200 Subject: [PATCH 25/87] test: qualify queue actions in postman contract --- src/engineering_platform/server.py | 4 +- tests/engineering/test_submission_service.py | 4 ++ .../http_json_postman_contract.py | 66 ++++++++++++++++++- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 9bc89e7f..4084bca7 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -3263,10 +3263,10 @@ def _delegate_dashboard(self, method: str) -> None: disposition=str(payload["disposition"]), reason=str(payload["reason"]), ) self._send(200, result) - except (ValueError, UnicodeDecodeError, json.JSONDecodeError): - self._send(400, {"error": "INVALID_REQUEST"}) except submission_service.SubmissionError as error: self._send(error.status, {"error": error.code}) + except (ValueError, UnicodeDecodeError, json.JSONDecodeError): + self._send(400, {"error": "INVALID_REQUEST"}) return if isinstance(selected, str) and selected in project_ids: # No supported CENTRAL Console route may fall through to the diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 0a295b2d..947377f4 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -120,6 +120,10 @@ def action(disposition: str, reason: str) -> dict[str, object]: self.assertEqual(action("QUARANTINED", "Investigate the source envelope")["state"], "QUARANTINED") self.assertEqual(action("QUEUED", "Investigation completed")["state"], "QUEUED") self.assertEqual(action("DECLINED", "The request is no longer needed")["state"], "DECLINED") + body = json.dumps({"submission_id": submitted.submission_id, "disposition": "QUEUED", "reason": "Must not revive a declined request"}).encode() + with self.assertRaises(HTTPError) as rejected: + urlopen(Request(endpoint, data=body, method="POST", headers={"Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}"})) # nosec B310 + self.assertEqual(rejected.exception.code, 409) with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: events = [row[0] for row in connection.execute( "SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,) diff --git a/tools/qualification/http_json_postman_contract.py b/tools/qualification/http_json_postman_contract.py index 07d0de72..082f817f 100644 --- a/tools/qualification/http_json_postman_contract.py +++ b/tools/qualification/http_json_postman_contract.py @@ -8,12 +8,13 @@ from pathlib import Path import re import socket +import sqlite3 import tempfile from threading import Thread from urllib.error import HTTPError from urllib.request import Request, urlopen -from engineering_platform import server +from engineering_platform import server, submission_service COLLECTION = Path("tests/engineering/postman/http-json-api.postman_collection.json") @@ -94,6 +95,68 @@ def _request(base_url: str, item: dict[str, object]) -> int: return error.code +def _queue_action_contract(data_root: Path, base_url: str) -> None: + """Exercise Operations Console actions through their real HTTP boundary. + + These are intentionally separate from the public OpenAPI collection: the + queue controls are authenticated local-console operations, not producer + transport endpoints. Keeping the distinction prevents a console write + from being silently advertised as a public API operation. + """ + project, repository = "postman-queue", "postman-queue-repository" + declaration = { + "schema_version": "1.0", + "project": {"id": project, "authority_repository_id": repository}, + "repository": {"id": repository, "role": "authority"}, + "validation": {"kind": "none"}, + } + with sqlite3.connect(data_root / server.SERVER_DATABASE_FILENAME) as connection: + server.project_topology.register_server_local_topology(connection, declaration=declaration) + credential = str(submission_service.issue_consumer_credential( + connection, consumer_id="postman-queue", project_id=project, + )["credential"]) + payload = { + "repository_id": repository, + "producer": {"id": "postman-queue", "type": "HUMAN", "version": "1"}, + "prompt": "Postman Operations Console queue qualification", + "idempotency_key": "postman-queue-actions", + } + submit = Request( + base_url + f"/v1/projects/{project}/submissions", data=json.dumps(payload).encode(), method="POST", + headers={"Content-Type": "application/json", "Authorization": f"Bearer {credential}"}, + ) + with urlopen(submit, timeout=3) as response: # nosec B310 + submission_id = str(json.loads(response.read())["submission_id"]) + + def action(disposition: str, reason: str, *, origin: str | None = None) -> tuple[int, str]: + request = Request( + base_url + f"/api/queue-disposition?project={project}", + data=json.dumps({"submission_id": submission_id, "disposition": disposition, "reason": reason}).encode(), + method="POST", headers={"Content-Type": "application/json", "Origin": origin or base_url}, + ) + try: + with urlopen(request, timeout=3) as response: # nosec B310 + response.read() + return response.status, "" + except HTTPError as error: + return error.code, error.read().decode("utf-8", "replace") + + for disposition, reason in ( + ("DEFERRED", "Postman defer contract"), ("QUEUED", "Postman resume contract"), + ("QUARANTINED", "Postman quarantine contract"), ("QUEUED", "Postman resume after quarantine contract"), + ("DECLINED", "Postman decline contract"), + ): + status, detail = action(disposition, reason) + if status != 200: + raise RuntimeError(f"POSTMAN_QUEUE_ACTION_FAILED:{disposition}:{status}:{detail}") + status, detail = action("QUEUED", "Must not revive a declined submission") + if status != 409: + raise RuntimeError(f"POSTMAN_QUEUE_TERMINAL_TRANSITION_NOT_REJECTED:{status}:{detail}") + status, detail = action("DEFERRED", "Untrusted origin", origin="https://untrusted.example") + if status != 403: + raise RuntimeError("POSTMAN_QUEUE_ORIGIN_ISOLATION_FAILED") + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--source-root", type=Path, required=True) @@ -123,6 +186,7 @@ def main(argv: list[str] | None = None) -> int: observed, expected = _request(base_url, item), _expected_status(item) if observed != expected: raise RuntimeError(f"API_POSTMAN_DRIFT {item.get('name')}: expected {expected}, got {observed}") + _queue_action_contract(data_root, base_url) finally: http_server.shutdown() worker.join(timeout=3) From dc924af6dadefcb743d79b091ad137afe85b18e5 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:22:24 +0200 Subject: [PATCH 26/87] docs: classify central queue disposition route --- src/engineering_platform/console_route_ownership.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engineering_platform/console_route_ownership.py b/src/engineering_platform/console_route_ownership.py index f0d44d37..818c7a1d 100644 --- a/src/engineering_platform/console_route_ownership.py +++ b/src/engineering_platform/console_route_ownership.py @@ -60,6 +60,7 @@ def matches(self, method: str, path: str) -> bool: ConsoleRoute(("GET",), r"/api/prompt-history/[a-z0-9][a-z0-9-]{0,63}/(?:report|chat|details)", PROJECT, "project_history", "Project run detail"), ConsoleRoute(("GET",), r"/api/telemetry/[0-9]{4}-[0-9]{2}-[0-9]{2}", PROJECT, "project_history", "Project telemetry detail"), ConsoleRoute(("POST",), r"/api/execution-(?:dismiss|retry)", PROJECT, "project_execution", "Project execution action"), + ConsoleRoute(("POST",), r"/api/queue-disposition", PROJECT, "project_execution", "Project queue disposition action"), ConsoleRoute(("POST",), r"/api/dashboard-translate", PROJECT, "project_console", "Project Console translation"), ConsoleRoute(("GET",), r"/diagnostics/topology", TRANSPORT_INTERNAL, "transport", "Transport topology diagnostic"), ConsoleRoute(("GET",), r"/(?:healthz|readyz)", TRANSPORT_INTERNAL, "transport", "Transport probe"), From da23c839c7d292f15aa229d3db00dd542b17a407 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:35:31 +0200 Subject: [PATCH 27/87] test: verify central queue action confirmations --- tests/engineering/dashboard.spec.mjs | 39 ++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/tests/engineering/dashboard.spec.mjs b/tests/engineering/dashboard.spec.mjs index 18018d95..f8c4f004 100644 --- a/tests/engineering/dashboard.spec.mjs +++ b/tests/engineering/dashboard.spec.mjs @@ -9919,8 +9919,13 @@ test.describe("Engineering Status browser smoke", () => { await expect(page.locator("#queueList")).not.toContainText("Later uitvoeren"); }); - test("renders CENTRAL FIFO items without offering a legacy file deferral", async ({ page }) => { + test("maps CENTRAL queue actions to their matching confirmation modal", async ({ page }) => { + await page.route("**/api/dashboard-snapshot", (route) => route.fulfill({ json: { + status: { watcher_state: "WATCHER_IDLE", queue_depth: 0, queue_items: [] }, + component_versions: {}, telemetry: [], duration_estimate: {}, build_commit: "", + } })); await page.goto(dashboardUrl, { waitUntil: "domcontentloaded" }); + await selectDashboardLocale(page, "nl"); await page.locator("#autoRefresh").uncheck(); await page.evaluate(() => queueItems([ { @@ -9931,10 +9936,40 @@ test.describe("Engineering Status browser smoke", () => { action_intent: "UNSPECIFIED", modified_at: "2026-08-02T10:01:00Z", queue_source: "CENTRAL", + queue_state: "DEFERRED", }, ], 1)); await expect(page.locator("#queueList .queue-item")).toHaveCount(1); - await expect(page.locator("#queueList .queue-defer")).toHaveCount(0); + await page.locator("#queueItems").evaluate((element) => { element.open = true; }); + const messages = DASHBOARD_MESSAGES[await page.locator("html").getAttribute("lang")]; + expect(await page.locator("#queueList button").allTextContents()).toEqual([messages["queue.resume_action"]]); + const resume = page.getByRole("button", { name: messages["queue.resume_action"], exact: true }); + await expect(resume).toHaveCount(1); + await expect(page.getByRole("button", { name: messages["queue.defer_action"], exact: true })).toHaveCount(0); + await resume.click(); + await expect(page.locator("#confirmationModalTitle")).toHaveText(messages["queue.resume_title"]); + await expect(page.locator("#confirmationModalText")).toHaveText( + messages["queue.resume_description"].replace("{title}", "sub-central-fifo"), + ); + await expect(page.locator("#confirmationModalConfirm")).toHaveText(messages["queue.resume_action"]); + await page.locator("#confirmationModalCancel").click(); + + await page.evaluate(() => queueItems([{ + submission_id: "sub-central-fifo", filename: "sub-central-fifo", + title_kind: "producer_submission", producer_type: "CLI", action_intent: "UNSPECIFIED", + modified_at: "2026-08-02T10:01:00Z", queue_source: "CENTRAL", queue_state: "QUEUED", + }], 1)); + for (const [actionKey, titleKey] of [ + ["queue.defer_action", "queue.defer_title"], + ["queue.quarantine_action", "queue.quarantine_title"], + ["queue.decline_action", "queue.decline_title"], + ]) { + const action = messages[actionKey], title = messages[titleKey]; + await page.getByRole("button", { name: action, exact: true }).click(); + await expect(page.locator("#confirmationModalTitle")).toHaveText(title); + await expect(page.locator("#confirmationModalConfirm")).toHaveText(action); + await page.locator("#confirmationModalCancel").click(); + } }); test("keeps a waiting Inbox item when deferring is cancelled", async ({ page }) => { From 6314a294294e42902473f1703dfc835f2dd545eb Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:37:53 +0200 Subject: [PATCH 28/87] fix: stack central queue actions per row --- src/engineering_platform/assets/dashboard.css | 2 +- src/engineering_platform/assets/dashboard.js | 7 +++++-- tests/engineering/dashboard.spec.mjs | 2 ++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.css b/src/engineering_platform/assets/dashboard.css index f31ed72a..ad4d0b05 100644 --- a/src/engineering_platform/assets/dashboard.css +++ b/src/engineering_platform/assets/dashboard.css @@ -869,7 +869,7 @@ html:not([data-theme="light"]) .dashboard-modal-shell{--modal-header-surface:col .predecessor-retry::before{content:"↻";font:700 17px/1 system-ui;margin-right:8px;vertical-align:-1px} .execution-dismiss::before{content:"⊘"} #componentLogs .log-table{table-layout:auto;width:100%}#componentLogs .log-table th:last-child,#componentLogs .log-table td:last-child{width:100%} -#queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important} +#queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-item__actions{align-items:flex-end;display:flex;flex-direction:column;gap:6px;justify-self:end}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important} /* Shared semantic action variants prevent individual surfaces from drifting. */ .dashboard-action{align-items:center;border:1px solid;border-radius:50%;box-sizing:border-box;display:inline-flex;font:18px/1 system-ui;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px} diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index ba6adeb3..f1cf3e5c 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -914,6 +914,7 @@ function queueItems(x, queueDepth) { const row = document.createElement("li"), number = document.createElement("span"), body = document.createElement("div"), + actions = document.createElement("div"), title = document.createElement("span"), meta = document.createElement("div"), modified = Date.parse(item.modified_at || ""), @@ -928,6 +929,7 @@ function queueItems(x, queueDepth) { number.textContent = String(index + 1); title.className = "queue-item__title"; meta.className = "queue-item__meta"; + actions.className = "queue-item__actions"; title.textContent = displayTitle; meta.textContent = t("queue.filename", { filename, @@ -954,7 +956,7 @@ function queueItems(x, queueDepth) { } body.append(title, meta); row.append(number, body); - if (defer) row.append(defer); + if (defer) actions.append(defer); if (item.queue_source === "CENTRAL" && item.queue_state === "QUEUED") { [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."], ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]].forEach(([disposition, actionKey, reason]) => { @@ -966,9 +968,10 @@ function queueItems(x, queueDepth) { event.preventDefault(); event.stopPropagation(); queueDisposition(item, disposition, reason, action); }); - row.append(action); + actions.append(action); }); } + if (actions.childElementCount) row.append(actions); container.append(row); }); } diff --git a/tests/engineering/dashboard.spec.mjs b/tests/engineering/dashboard.spec.mjs index f8c4f004..79edc3eb 100644 --- a/tests/engineering/dashboard.spec.mjs +++ b/tests/engineering/dashboard.spec.mjs @@ -9941,6 +9941,7 @@ test.describe("Engineering Status browser smoke", () => { ], 1)); await expect(page.locator("#queueList .queue-item")).toHaveCount(1); await page.locator("#queueItems").evaluate((element) => { element.open = true; }); + await expect(page.locator("#queueList .queue-item__actions")).toHaveCount(1); const messages = DASHBOARD_MESSAGES[await page.locator("html").getAttribute("lang")]; expect(await page.locator("#queueList button").allTextContents()).toEqual([messages["queue.resume_action"]]); const resume = page.getByRole("button", { name: messages["queue.resume_action"], exact: true }); @@ -9970,6 +9971,7 @@ test.describe("Engineering Status browser smoke", () => { await expect(page.locator("#confirmationModalConfirm")).toHaveText(action); await page.locator("#confirmationModalCancel").click(); } + await expect(page.locator("#queueList .queue-item__actions .queue-defer")).toHaveCount(3); }); test("keeps a waiting Inbox item when deferring is cancelled", async ({ page }) => { From 5612c8a27c420d7c9424e509698c52eb9a5ff110 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:41:35 +0200 Subject: [PATCH 29/87] test: cover complete ep server http surface in postman --- ...erver-http-surface.postman_collection.json | 1118 +++++++++++++++++ .../ep_server_postman_surface.py | 98 ++ .../http_json_postman_contract.py | 35 +- 3 files changed, 1248 insertions(+), 3 deletions(-) create mode 100644 tests/engineering/postman/ep-server-http-surface.postman_collection.json create mode 100644 tools/qualification/ep_server_postman_surface.py diff --git a/tests/engineering/postman/ep-server-http-surface.postman_collection.json b/tests/engineering/postman/ep-server-http-surface.postman_collection.json new file mode 100644 index 00000000..951ef29d --- /dev/null +++ b/tests/engineering/postman/ep-server-http-surface.postman_collection.json @@ -0,0 +1,1118 @@ +{ + "info": { + "name": "EP Server complete HTTP surface", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Console shell", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Console JavaScript asset", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/assets/dashboard.js" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Console favicon", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/favicon.ico" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Platform health", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/health" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(503));" + ] + } + } + ] + }, + { + "name": "Platform projection", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/platform-status" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Dashboard snapshot", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/dashboard-snapshot" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Status alias", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/status" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Platform component details", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/components/ep_server/details" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Invalid component details", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/components/not_a_component/details" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Component restart rejects malformed body", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/components/ep_server/restart", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Platform logs", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/logs/all" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Platform logs reject malformed clear", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/logs/all", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(400));" + ] + } + } + ] + }, + { + "name": "Provider readiness", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/provider-login-status" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Provider repair rejects malformed body", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/provider-login/repair", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Provider logout rejects malformed body", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/provider-login/logout", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Execution runtime", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/execution-runtime-status" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Runtime repair rechecks the installed runtime", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/execution-runtime/repair", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Provider capacity", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/provider-capacity" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Provider capacity configuration", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/provider-capacity/configuration" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Provider capacity rejects malformed update", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/provider-capacity/configuration", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "GitHub rate-limit diagnostics", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/github-rate-limit" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Console configuration", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/configuration" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Console configuration rejects malformed update", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/configuration", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Process metrics", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/process-metrics" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Usage diagnostics", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/usage" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Host-admin diagnostics", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/host-admin/diagnostics" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Central data export", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/central-data/export" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Central relocation rejects bad request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/central-data/relocate", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Central import requires confirmation", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/central-data/import", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(409));" + ] + } + } + ] + }, + { + "name": "Central database configuration", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/central-database/configuration" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Central database configuration rejects bad request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/central-database/configuration", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(400));" + ] + } + } + ] + }, + { + "name": "Project history", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/prompt-history?project=postman-project" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Missing project report", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/prompt-history/missing-run/report?project=postman-project" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(404));" + ] + } + } + ] + }, + { + "name": "Missing project chat", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/prompt-history/missing-run/chat?project=postman-project" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(404));" + ] + } + } + ] + }, + { + "name": "Missing project detail", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/prompt-history/missing-run/details?project=postman-project" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(404));" + ] + } + } + ] + }, + { + "name": "Missing telemetry day", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/telemetry/2026-01-01?project=postman-project" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(404));" + ] + } + } + ] + }, + { + "name": "Execution action rejects malformed request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Origin", + "value": "{{baseUrl}}" + } + ], + "url": "{{baseUrl}}/api/execution-dismiss?project=postman-project", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(400));" + ] + } + } + ] + }, + { + "name": "Queue action rejects malformed request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Origin", + "value": "{{baseUrl}}" + } + ], + "url": "{{baseUrl}}/api/queue-disposition?project=postman-project", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(400));" + ] + } + } + ] + }, + { + "name": "Translation route is fail-closed pending project mutation support", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Origin", + "value": "{{baseUrl}}" + } + ], + "url": "{{baseUrl}}/api/dashboard-translate?project=postman-project", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(405));" + ] + } + } + ] + }, + { + "name": "Retired runtime directory", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/runtime-directory/open", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(410));" + ] + } + } + ] + }, + { + "name": "Retired Inbox configuration", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/api/configuration/inbox-location", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(410));" + ] + } + } + ] + }, + { + "name": "Retired component logs", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/logs/inbox" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(410));" + ] + } + } + ] + }, + { + "name": "Operations projects", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/v1/operations/projects" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Topology diagnostics", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/diagnostics/topology" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Health probe", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/healthz" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Readiness probe", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/readyz" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(200));" + ] + } + } + ] + }, + { + "name": "Submission requires credential", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/v1/projects/postman-project/submissions", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(401));" + ] + } + } + ] + }, + { + "name": "Submission readback requires credential", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/v1/projects/postman-project/submissions/missing" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(401));" + ] + } + } + ] + }, + { + "name": "Evidence artifact requires credential", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/v1/projects/postman-project/artifacts/terminal-evidence:missing" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(401));" + ] + } + } + ] + }, + { + "name": "Agent pairing rejects malformed request", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/v1/agent/pair", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(400));" + ] + } + } + ] + }, + { + "name": "Agent registration requires credential", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": "{{baseUrl}}/v1/agent/register", + "body": { + "mode": "raw", + "raw": "{}" + } + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(401));" + ] + } + } + ] + }, + { + "name": "Unknown route", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/not-an-ep-route" + }, + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('expected status', () => pm.response.to.have.status(404));" + ] + } + } + ] + } + ] +} diff --git a/tools/qualification/ep_server_postman_surface.py b/tools/qualification/ep_server_postman_surface.py new file mode 100644 index 00000000..521b4a15 --- /dev/null +++ b/tools/qualification/ep_server_postman_surface.py @@ -0,0 +1,98 @@ +"""Complete, fail-closed Postman surface manifest for EP Server. + +This is deliberately distinct from the public OpenAPI collection: it covers +the installed Operations Console, administration and retired compatibility +routes as well as producer transport. Every item is a callable HTTP request +with its intended status code, so a route cannot silently disappear or start +accepting an unsafe request without changing the qualification manifest. +""" +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class Case: + name: str + method: str + path: str + status: int + body: str | None = None + project: bool = False + origin: bool = False + + +# One concrete case for every route family in console_route_ownership, plus +# transport aliases that are intentionally outside that Console matrix. +CASES: tuple[Case, ...] = ( + Case("Console shell", "GET", "/", 200), + Case("Console JavaScript asset", "GET", "/assets/dashboard.js", 200), + Case("Console favicon", "GET", "/favicon.ico", 200), + Case("Platform health", "GET", "/health", 503), + Case("Platform projection", "GET", "/api/platform-status", 200), + Case("Dashboard snapshot", "GET", "/api/dashboard-snapshot", 200), + Case("Status alias", "GET", "/api/status", 200), + Case("Platform component details", "GET", "/api/components/ep_server/details", 200), + Case("Invalid component details", "GET", "/api/components/not_a_component/details", 409), + Case("Component restart rejects malformed body", "POST", "/api/components/ep_server/restart", 409, "{}"), + Case("Platform logs", "GET", "/api/logs/all", 200), + Case("Platform logs reject malformed clear", "POST", "/api/logs/all", 400, "{}"), + Case("Provider readiness", "GET", "/api/provider-login-status", 200), + Case("Provider repair rejects malformed body", "POST", "/api/provider-login/repair", 409, "{}"), + Case("Provider logout rejects malformed body", "POST", "/api/provider-login/logout", 409, "{}"), + Case("Execution runtime", "GET", "/api/execution-runtime-status", 200), + Case("Runtime repair rechecks the installed runtime", "POST", "/api/execution-runtime/repair", 200, "{}"), + Case("Provider capacity", "GET", "/api/provider-capacity", 200), + Case("Provider capacity configuration", "GET", "/api/provider-capacity/configuration", 200), + Case("Provider capacity rejects malformed update", "POST", "/api/provider-capacity/configuration", 409, "{}"), + Case("GitHub rate-limit diagnostics", "GET", "/api/github-rate-limit", 200), + Case("Console configuration", "GET", "/api/configuration", 200), + Case("Console configuration rejects malformed update", "POST", "/api/configuration", 409, "{}"), + Case("Process metrics", "GET", "/api/process-metrics", 200), + Case("Usage diagnostics", "GET", "/api/usage", 200), + Case("Host-admin diagnostics", "GET", "/api/host-admin/diagnostics", 200), + Case("Central data export", "GET", "/api/central-data/export", 200), + Case("Central relocation rejects bad request", "POST", "/api/central-data/relocate", 409, "{}"), + Case("Central import requires confirmation", "POST", "/api/central-data/import", 409, "{}"), + Case("Central database configuration", "GET", "/api/central-database/configuration", 200), + Case("Central database configuration rejects bad request", "POST", "/api/central-database/configuration", 400, "{}"), + Case("Project history", "GET", "/api/prompt-history", 200, project=True), + Case("Missing project report", "GET", "/api/prompt-history/missing-run/report", 404, project=True), + Case("Missing project chat", "GET", "/api/prompt-history/missing-run/chat", 404, project=True), + Case("Missing project detail", "GET", "/api/prompt-history/missing-run/details", 404, project=True), + Case("Missing telemetry day", "GET", "/api/telemetry/2026-01-01", 404, project=True), + Case("Execution action rejects malformed request", "POST", "/api/execution-dismiss", 400, "{}", True, True), + Case("Queue action rejects malformed request", "POST", "/api/queue-disposition", 400, "{}", True, True), + Case("Translation route is fail-closed pending project mutation support", "POST", "/api/dashboard-translate", 405, "{}", True, True), + Case("Retired runtime directory", "POST", "/api/runtime-directory/open", 410, "{}"), + Case("Retired Inbox configuration", "POST", "/api/configuration/inbox-location", 410, "{}"), + Case("Retired component logs", "GET", "/api/logs/inbox", 410), + Case("Operations projects", "GET", "/v1/operations/projects", 200), + Case("Topology diagnostics", "GET", "/diagnostics/topology", 200), + Case("Health probe", "GET", "/healthz", 200), + Case("Readiness probe", "GET", "/readyz", 200), + Case("Submission requires credential", "POST", "/v1/projects/postman-project/submissions", 401, "{}"), + Case("Submission readback requires credential", "GET", "/v1/projects/postman-project/submissions/missing", 401), + Case("Evidence artifact requires credential", "GET", "/v1/projects/postman-project/artifacts/terminal-evidence:missing", 401), + Case("Agent pairing rejects malformed request", "POST", "/v1/agent/pair", 400, "{}"), + Case("Agent registration requires credential", "POST", "/v1/agent/register", 401, "{}"), + Case("Unknown route", "GET", "/not-an-ep-route", 404), +) + + +def postman_collection() -> dict[str, object]: + def item(case: Case) -> dict[str, object]: + headers = ([{"key": "Content-Type", "value": "application/json"}] + if case.body is not None else []) + if case.origin: + headers.append({"key": "Origin", "value": "{{baseUrl}}"}) + suffix = "?project=postman-project" if case.project else "" + request: dict[str, object] = {"method": case.method, "header": headers, + "url": "{{baseUrl}}" + case.path + suffix} + if case.body is not None: + request["body"] = {"mode": "raw", "raw": case.body} + return {"name": case.name, "request": request, "event": [{"listen": "test", "script": {"exec": [ + f"pm.test('expected status', () => pm.response.to.have.status({case.status}));" + ]}}]} + return {"info": {"name": "EP Server complete HTTP surface", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"}, + "item": [item(case) for case in CASES]} diff --git a/tools/qualification/http_json_postman_contract.py b/tools/qualification/http_json_postman_contract.py index 082f817f..1d301305 100644 --- a/tools/qualification/http_json_postman_contract.py +++ b/tools/qualification/http_json_postman_contract.py @@ -10,17 +10,27 @@ import socket import sqlite3 import tempfile -from threading import Thread +from threading import Lock, Thread from urllib.error import HTTPError from urllib.request import Request, urlopen from engineering_platform import server, submission_service +from ep_server_postman_surface import postman_collection COLLECTION = Path("tests/engineering/postman/http-json-api.postman_collection.json") +SURFACE_COLLECTION = Path("tests/engineering/postman/ep-server-http-surface.postman_collection.json") _STATUS = re.compile(r"pm\.response\.to\.have\.status\((\d+)\)") +class _NoopService: + """Minimal lifecycle double for isolated HTTP surface qualification.""" + def start(self) -> None: pass + def stop(self) -> None: pass + def diagnostics(self) -> "_NoopService": return self + def to_dict(self) -> dict[str, object]: return {"state": "QUALIFICATION_NOOP"} + + def _operations_from_openapi(document: dict[str, object]) -> set[tuple[str, str]]: paths = document.get("paths") if not isinstance(paths, dict): @@ -57,7 +67,9 @@ def _collection_operation(item: dict[str, object]) -> tuple[str, str]: if not isinstance(method, str) or not isinstance(raw, str): raise RuntimeError("POSTMAN_REQUEST_INVALID") path = raw.removeprefix("{{baseUrl}}") - path = re.sub(r":([A-Za-z_][A-Za-z0-9_]*)", r"{\1}", path) + # Postman's ``:name`` parameters occur at a path-segment boundary. Do + # not rewrite a literal colon in an evidence-artifact identifier. + path = re.sub(r"/:([A-Za-z_][A-Za-z0-9_]*)", r"/{\1}", path) if not path.startswith("/"): raise RuntimeError("POSTMAN_URL_INVALID") return method.upper(), path @@ -84,7 +96,7 @@ def _request(base_url: str, item: dict[str, object]) -> int: assert isinstance(request, dict) method, path = _collection_operation(item) url = base_url + path.replace("{project_id}", "postman-project") - headers = {str(header["key"]): str(header["value"]) + headers = {str(header["key"]): str(header["value"]).replace("{{baseUrl}}", base_url) for header in request.get("header", []) if isinstance(header, dict) and "key" in header and "value" in header} body = request.get("body", {}) data = str(body.get("raw", "")).encode("utf-8") if isinstance(body, dict) and method != "GET" else None @@ -162,8 +174,11 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--source-root", type=Path, required=True) root = parser.parse_args(argv).source_root.resolve() collection = json.loads((root / COLLECTION).read_text(encoding="utf-8")) + surface_collection = json.loads((root / SURFACE_COLLECTION).read_text(encoding="utf-8")) if collection.get("info", {}).get("schema") != "https://schema.getpostman.com/json/collection/v2.1.0/collection.json": raise RuntimeError("POSTMAN_SCHEMA_INVALID") + if surface_collection != postman_collection(): + raise RuntimeError("POSTMAN_SERVER_SURFACE_MANIFEST_DRIFT") expected_openapi = server._http_json_openapi_document() items = _collection_items(collection.get("item")) if _operations_from_openapi(expected_openapi) != {_collection_operation(item) for item in items}: @@ -172,8 +187,18 @@ def main(argv: list[str] | None = None) -> int: data_root = Path(temporary) / "data" port = _port() server.initialize(data_root, bind_port=port) + with sqlite3.connect(data_root / server.SERVER_DATABASE_FILENAME) as connection: + server.project_topology.register_server_local_topology(connection, declaration={ + "schema_version": "1.0", "project": {"id": "postman-project", "authority_repository_id": "postman-repository"}, + "repository": {"id": "postman-repository", "role": "authority"}, "validation": {"kind": "none"}, + }) http_server = http.server.ThreadingHTTPServer(("127.0.0.1", port), server._HealthHandler) http_server.data_root = data_root # type: ignore[attr-defined] + http_server.central_data_transfer_lock = Lock() # type: ignore[attr-defined] + http_server.central_data_transfer_active = False # type: ignore[attr-defined] + http_server.dependabot_service = _NoopService() # type: ignore[attr-defined] + http_server.inbox_service = _NoopService() # type: ignore[attr-defined] + http_server.lifecycle_worker = _NoopService() # type: ignore[attr-defined] worker = Thread(target=http_server.serve_forever, daemon=True) worker.start() try: @@ -186,6 +211,10 @@ def main(argv: list[str] | None = None) -> int: observed, expected = _request(base_url, item), _expected_status(item) if observed != expected: raise RuntimeError(f"API_POSTMAN_DRIFT {item.get('name')}: expected {expected}, got {observed}") + for item in _collection_items(surface_collection.get("item")): + observed, expected = _request(base_url, item), _expected_status(item) + if observed != expected: + raise RuntimeError(f"SERVER_SURFACE_POSTMAN_DRIFT {item.get('name')}: expected {expected}, got {observed}") _queue_action_contract(data_root, base_url) finally: http_server.shutdown() From 51eb8c3ae653bc1b0f1aacc43806faa09b4f7a37 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:48:20 +0200 Subject: [PATCH 30/87] test: gate deterministic execution e2e in ci --- .github/workflows/engineering-platform-validation.yml | 5 +++++ .github/workflows/ep-server-production-release.yml | 3 +++ src/engineering_platform/execution_host.py | 10 +++++++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/engineering-platform-validation.yml b/.github/workflows/engineering-platform-validation.yml index d16e764b..16bb4e44 100644 --- a/.github/workflows/engineering-platform-validation.yml +++ b/.github/workflows/engineering-platform-validation.yml @@ -76,6 +76,11 @@ jobs: python3 -m unittest discover -s tests -p 'test_*.py' - name: Qualify installed P-TRANSPORT 3×2 ingress matrix run: python3 tools/qualification/p_transport_installed_ingress_matrix.py --source-root . + - name: Qualify deterministic Genesis and Managed execution E2E + # The E2E script creates an isolated CENTRAL data root and a local bare + # Git origin for Managed. CI therefore exercises both execution modes + # without mutating a GitHub repository. + run: python3 tools/qualification/p_deterministic_execution_e2e.py --source-root . - name: Qualify HTTP JSON API, OpenAPI and Postman contract run: PYTHONPATH=src python3 tools/qualification/http_json_postman_contract.py --source-root . - name: Enforce Engineering Platform coverage contract diff --git a/.github/workflows/ep-server-production-release.yml b/.github/workflows/ep-server-production-release.yml index 30285e47..1285d636 100644 --- a/.github/workflows/ep-server-production-release.yml +++ b/.github/workflows/ep-server-production-release.yml @@ -101,6 +101,9 @@ jobs: python3 tools/qualification/platform_version_consistency.py --source-root . python3 -m unittest discover -s tests -p 'test_*.py' python3 tools/qualification/p_transport_installed_ingress_matrix.py --source-root . + # This uses an isolated CENTRAL root and local bare Git origin; the + # release gate never writes to an external GitHub repository. + python3 tools/qualification/p_deterministic_execution_e2e.py --source-root . PYTHONPATH=src python3 tools/qualification/http_json_postman_contract.py --source-root . - name: Enforce production coverage and security gates run: | diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 577b3585..95dd1bbb 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -542,7 +542,15 @@ def _provider_readiness_gate( agent action requires Codex too. The saved original action lets a verified resume continue exactly where it stopped. """ - missing = provider_readiness_failures(self.root, require_github=require_github) + # The installed deterministic qualification composes a local GitHub + # adapter before the runner is created. It must therefore qualify the + # Managed lifecycle against that adapter, rather than demand an + # unrelated interactive GitHub session from the CI runner. + qualification_local_github = os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + missing = provider_readiness_failures( + self.root, + require_github=require_github and not qualification_local_github, + ) if not require_codex: missing = tuple(provider for provider in missing if provider != "CODEX") if missing: From bb8af425d38db5abf0c56d9eff8b97ebc08de07c Mon Sep 17 00:00:00 2001 From: pcvantol Date: Mon, 7 Sep 2026 23:50:55 +0200 Subject: [PATCH 31/87] fix: use canonical central database in e2e qualification --- .../qualification/p_deterministic_execution_e2e.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 1c5a2727..11127f04 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -22,6 +22,9 @@ from urllib.request import Request, urlopen +CENTRAL_DATABASE_FILENAME = "epdata.sqlite" + + def command(binary: Path, *args: str) -> dict[str, object]: result = subprocess.run((str(binary), *args), check=True, text=True, capture_output=True) # nosec B603 return json.loads(result.stdout) @@ -74,7 +77,10 @@ def verify_receipt(data_root: Path, project: str, run_id: str) -> dict[str, obje observed = [(item.get("reviewer"), item.get("status")) for item in reviews] if isinstance(reviews, list) else [] if observed != [("quality", "PASS"), ("security", "PASS")]: raise RuntimeError(f"ASSURANCE_RECEIPT_INVALID: {observed}") - with sqlite3.connect(data_root / "engineering.db") as connection: + # The standalone Server owns epdata.sqlite. The former engineering.db + # name is deliberately retired and must never become an accidental second + # lifecycle authority during qualification. + with sqlite3.connect(data_root / CENTRAL_DATABASE_FILENAME) as connection: row = connection.execute("SELECT phase,payload FROM engineering_transactions WHERE run_id=?", (run_id,)).fetchone() if row is None or row[0] != "COMPLETE": raise RuntimeError("TERMINAL_CHECKPOINT_UNAVAILABLE") @@ -127,7 +133,11 @@ def main(argv: list[str] | None = None) -> int: if project == "managed" and args.managed_repository: raise RuntimeError("MANAGED_GITHUB_FIXTURE_NEEDS_MATCHING_DECLARATION") command(server, "bind-repository", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) - env = {**os.environ, "EP_QUALIFICATION_DETERMINISTIC_FLOW": "1", "EP_CENTRAL_OPERATIONAL_DATABASE": str(data / "engineering.db")} + env = { + **os.environ, + "EP_QUALIFICATION_DETERMINISTIC_FLOW": "1", + "EP_CENTRAL_OPERATIONAL_DATABASE": str(data / CENTRAL_DATABASE_FILENAME), + } process = subprocess.Popen((str(server), "serve", "--data-root", str(data)), env=env) # nosec B603 try: base = f"http://127.0.0.1:{bind_port}" From b1a6c69541f1c609b0a1669df6a8a4a2eab151ca Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:00:57 +0200 Subject: [PATCH 32/87] fix: resume initialized parity dispatches --- src/engineering_platform/parity_lifecycle_dispatcher.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index 8112858b..49feeca2 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -492,7 +492,12 @@ def dispatch(self, submission_id: str) -> DispatchReceipt: return DispatchReceipt(submission_id, context.project_id, context.repository_id, run_id, "RUNNING", duplicate) try: with _historical_admission_environment(repository_root, self.data_root): - if not duplicate: + # INITIALIZE_ONLY qualification deliberately allocates the + # canonical dispatch before writing the runner input. A + # later normal resume refers to that same dispatch, but must + # still materialize the input exactly once before invoking + # the runner. + 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( From aa08933925004a9d157472fbf579dc30d8eb7f58 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:04:09 +0200 Subject: [PATCH 33/87] test: document initialized dispatch recovery --- docs/engineering/EXECUTION_HOST_OPERATIONS.md | 18 ++++++++++++++++++ .../test_parity_lifecycle_dispatcher.py | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index 4d3cf5e9..341d3dc8 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -144,6 +144,24 @@ once consumed it cannot be disarmed or fired again. Prompt text and submission provenance cannot create this control, and it never creates a retry submission or new root run. +For qualification, run the control together with the recovery tests rather +than treating an armed marker as a passing result. The required evidence is: +`ARMED` before the boundary, one `CONSUMED` control artifact at +`QUALITY_CONTROL_AGENT`, one recovery/retry lineage, and one terminal outcome. +The deterministic Genesis/Managed installed E2E lives in +`tools/qualification/p_deterministic_execution_e2e.py`; it uses an isolated +CENTRAL root and local Git fixture, and deliberately does not use this operator +fault control. The dedicated provider-recovery test suite qualifies the armed +one-shot and retry semantics. + +## CENTRAL project lanes + +CENTRAL retains a FIFO lane per project. It permits at most one active, +blocked-with-open-resolution, or retry-pending run in that project. The +Lifecycle Worker may dispatch one eligible run from each different project in +parallel, so a Genesis and a Managed run in separate projects may overlap; a +second run in either same project cannot. + ## Local repository validation gate Validation is selected from the actual bounded-branch diff. Documentation and diff --git a/tests/engineering/test_parity_lifecycle_dispatcher.py b/tests/engineering/test_parity_lifecycle_dispatcher.py index 5bbfab5d..55d5e822 100644 --- a/tests/engineering/test_parity_lifecycle_dispatcher.py +++ b/tests/engineering/test_parity_lifecycle_dispatcher.py @@ -151,6 +151,23 @@ def test_claims_one_submission_once_and_preserves_central_run_linkage(self) -> N self.assertFalse((self.roots["alpha"] / ".engineering" / "engineering.db").exists()) self.assertFalse((self.roots["alpha"] / ".engineering" / "engineering-runs").exists()) + def test_initialize_only_dispatch_materializes_input_on_normal_resume(self) -> None: + """A visible pre-run dispatch remains resumable after qualification pauses it.""" + submission = self._submission("alpha") + dispatcher = ParityLifecycleDispatcher(self.data, runner_factory=lambda root: _Runner()) + with patch.dict(os.environ, {"EP_QUALIFICATION_INITIALIZE_ONLY": "1"}, clear=False): + initialized = dispatcher.dispatch(submission) + prompt = self.data / "artifacts" / "projects" / "alpha" / "runs" / initialized.run_id / "submission.md" + self.assertEqual(initialized.state, "RUNNING") + self.assertFalse(prompt.exists()) + 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()): + resumed = dispatcher.dispatch(submission) + self.assertTrue(resumed.duplicate_claim) + self.assertEqual(resumed.state, "COMPLETE") + self.assertTrue(prompt.is_file()) + def test_context_never_crosses_project_binding(self) -> None: alpha, beta = self._submission("alpha"), self._submission("beta") dispatcher = ParityLifecycleDispatcher(self.data, runner_factory=lambda root: _Runner()) From aa8138453f9972f67836d52b8f6ba200e6c110af Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:14:18 +0200 Subject: [PATCH 34/87] test: qualify installed controlled recovery flow --- docs/engineering/EXECUTION_HOST_OPERATIONS.md | 21 +++++--- src/engineering_platform/execution_host.py | 7 +++ src/engineering_platform/provider_recovery.py | 34 +++++++++---- .../qualification_runtime.py | 26 ++++++++++ .../test_coverage_runtime_boundaries.py | 24 ++++----- .../test_provider_recovery_controller.py | 28 +++++------ .../p_deterministic_execution_e2e.py | 49 ++++++++++++++++++- 7 files changed, 145 insertions(+), 44 deletions(-) diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index 341d3dc8..477fa0e5 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -127,12 +127,13 @@ terminal date, and a repeated recovery cannot add a second run or count. ## Controlled provider-interruption qualification proof For the dedicated, one-shot recovery proof, an operator may arm only an -already-admitted, non-terminal run before it reaches `QUALITY_CONTROL_AGENT`: +already-admitted, non-terminal run before it reaches `EXECUTE_AGENT`: ```sh python3 -m tools.engineering.provider_recovery arm-controlled-interruption \ --repo /Users/pcvantol/Documents/GitHub/djconnect \ - --run-id --phase QUALITY_CONTROL_AGENT + --run-id --phase EXECUTE_AGENT \ + --central-database /epdata.sqlite ``` Inspect the exact control with `controlled-interruption-status` and cancel an @@ -147,12 +148,16 @@ or new root run. For qualification, run the control together with the recovery tests rather than treating an armed marker as a passing result. The required evidence is: `ARMED` before the boundary, one `CONSUMED` control artifact at -`QUALITY_CONTROL_AGENT`, one recovery/retry lineage, and one terminal outcome. -The deterministic Genesis/Managed installed E2E lives in -`tools/qualification/p_deterministic_execution_e2e.py`; it uses an isolated -CENTRAL root and local Git fixture, and deliberately does not use this operator -fault control. The dedicated provider-recovery test suite qualifies the armed -one-shot and retry semantics. +`EXECUTE_AGENT`, one recovery/retry lineage, and one terminal outcome. +The deterministic installed E2E lives in +`tools/qualification/p_deterministic_execution_e2e.py`. It uses an isolated +CENTRAL root and local Git fixture to exercise Genesis, Managed, and a third +controlled-recovery lane. The third lane pauses only its deterministic +qualification adapter after the canonical `INITIALIZE` checkpoint, arms the +real run-bound control through the installed CLI, and verifies `CONSUMED`, one +same-run `RECOVERED` lineage, terminal assurance evidence, and the run in the +dashboard's project-scoped history. The dedicated provider-recovery unit suite +additionally qualifies unsafe and ambiguous recovery branches. ## CENTRAL project lanes diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index e6a998d4..b22a67f5 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -431,6 +431,7 @@ def _controlled_interruption_requested(self, state: TransactionState) -> bool: """ return consume_controlled_interruption_hook( self.root, run_id=state.run_id, phase=state.phase, + central_database=self.store.central_database, ) def _recovery_state(self, run_id: str) -> dict[str, object] | None: @@ -2109,6 +2110,12 @@ def run( state = replace(state, action_intent=context.action_intent) # Establish canonical transaction identity before persisting readiness evidence. self.store.save(state) + qualification_control_wait = getattr(self.agent, "wait_for_controlled_interruption_arm", None) + if callable(qualification_control_wait) and state.phase == "INITIALIZE": + # Only the deterministic installed-qualification adapter exposes + # this bounded rendezvous. Production adapters have no such + # method, so an operator control can never delay normal work. + qualification_control_wait(self.root, state) # This envelope is deliberately persisted once and can be resumed # after process restart. It is excluded from bottleneck ranking. self._total_phase = self._resume_phase( diff --git a/src/engineering_platform/provider_recovery.py b/src/engineering_platform/provider_recovery.py index 94f957d6..abb3a831 100644 --- a/src/engineering_platform/provider_recovery.py +++ b/src/engineering_platform/provider_recovery.py @@ -27,7 +27,10 @@ }) ACTIVE_RECOVERY_STATES = frozenset({"RECOVERY_AVAILABLE", "RECOVERY_STARTING", "RECOVERY_IN_PROGRESS"}) TERMINAL_RECOVERY_STATES = RECOVERY_STATES - ACTIVE_RECOVERY_STATES -CONTROLLED_INTERRUPTION_PHASES = frozenset({"QUALITY_CONTROL_AGENT"}) +# Quality assurance is now a read-only reviewer pipeline, not a provider turn. +# The only active provider boundary that can be interrupted and recovered is +# the implementation invocation. +CONTROLLED_INTERRUPTION_PHASES = frozenset({"EXECUTE_AGENT"}) CONTROL_DIRECTORY = Path(".engineering/artifacts/provider-recovery-fault-injection") @@ -64,24 +67,30 @@ def controlled_interruption_status(root: Path, *, run_id: str, phase: str) -> st return "ARMED" if armed.is_file() else "NOT_ARMED" -def _validate_control_target(root: Path, *, run_id: str, phase: str) -> object: +def _validate_control_target(root: Path, *, run_id: str, phase: str, + central_database: Path | None = None) -> object: if phase not in CONTROLLED_INTERRUPTION_PHASES: raise ControlledInterruptionControlError("phase is not supported for controlled interruption") try: - state = StateStore(root / ".engineering" / "engineering-runs").load(run_id) + state = StateStore( + root / ".engineering" / "engineering-runs", + central_database=central_database, + emit_local_projection=False, + ).load(run_id) except StateError as error: raise ControlledInterruptionControlError(str(error)) from error if state.terminal: raise ControlledInterruptionControlError("run is terminal") # The hook is deliberately offered only before its lifecycle boundary. A # phase already entered may have an active provider that cannot be raced. - if state.phase not in PHASES or state.phase in {"QUALITY_CONTROL_AGENT", "REPAIR_AGENT", "FINALIZE_AGENT", "RECONCILE_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE", "REPOSITORY_CLEANUP"}: + if state.phase not in PHASES or state.phase in {"EXECUTE_AGENT", "QUALITY_CONTROL_AGENT", "REPAIR_AGENT", "FINALIZE_AGENT", "RECONCILE_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE", "REPOSITORY_CLEANUP"}: raise ControlledInterruptionControlError("target phase is already active or has passed") return state -def arm_controlled_interruption(root: Path, *, run_id: str, phase: str, armed_by: str | None = None, reason: str | None = None) -> dict[str, object]: - _validate_control_target(root, run_id=run_id, phase=phase) +def arm_controlled_interruption(root: Path, *, run_id: str, phase: str, armed_by: str | None = None, + reason: str | None = None, central_database: Path | None = None) -> dict[str, object]: + _validate_control_target(root, run_id=run_id, phase=phase, central_database=central_database) armed, consumed = _control_paths(root, run_id, phase) if consumed.is_file(): raise ControlledInterruptionControlError("controlled interruption is already consumed") @@ -112,7 +121,8 @@ def disarm_controlled_interruption(root: Path, *, run_id: str, phase: str) -> st return "DISARMED" -def consume_controlled_interruption_hook(root: Path, *, run_id: str, phase: str) -> bool: +def consume_controlled_interruption_hook(root: Path, *, run_id: str, phase: str, + central_database: Path | None = None) -> bool: """Durably consume the explicit qualification-only interruption hook. The marker is a run-bound artifact rather than a provider recovery row: @@ -125,7 +135,7 @@ def consume_controlled_interruption_hook(root: Path, *, run_id: str, phase: str) requested = os.environ.get("ENGINEERING_PLATFORM_TEST_INTERRUPT_PROVIDER_ONCE") armed, path = _control_paths(root, run_id, phase) durable_armed = armed.is_file() - if (requested != f"{run_id}:{phase}" and not durable_armed) or load_recovery_state(root, run_id) is not None: + if (requested != f"{run_id}:{phase}" and not durable_armed) or load_recovery_state(root, run_id, central_database=central_database) is not None: return False artifact_id = f"provider-recovery-fault-injection:{run_id}:{phase}" directory = root / CONTROL_DIRECTORY @@ -159,6 +169,7 @@ def consume_controlled_interruption_hook(root: Path, *, run_id: str, phase: str) root, path, artifact_id=artifact_id, artifact_type="CONTROLLED_PROVIDER_INTERRUPTION", content_type="application/json", created_at=str(payload["consumed_at"]), run_id=run_id, + central_database=central_database, ) except Exception: # The exclusive marker remains intentionally: after an uncertain @@ -174,11 +185,16 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--run-id", required=True) parser.add_argument("--phase", required=True) parser.add_argument("--reason") + parser.add_argument("--central-database", type=Path, + help="Explicit CENTRAL lifecycle database for an installed Server run.") args = parser.parse_args(argv) root = args.repo.resolve() try: if args.command == "arm-controlled-interruption": - payload = arm_controlled_interruption(root, run_id=args.run_id, phase=args.phase, reason=args.reason) + payload = arm_controlled_interruption( + root, run_id=args.run_id, phase=args.phase, reason=args.reason, + central_database=args.central_database, + ) print(json.dumps({"status": "ARMED", **payload}, sort_keys=True)) elif args.command == "controlled-interruption-status": print(json.dumps({"status": controlled_interruption_status(root, run_id=args.run_id, phase=args.phase), "run_id": args.run_id, "phase": args.phase}, sort_keys=True)) diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index 6e57a3a4..f0d9d128 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -6,14 +6,40 @@ from __future__ import annotations from pathlib import Path +import json +import os import subprocess +import time from .capability_review import ReviewerResult from .execution_models import AgentResult, PullRequestEvidence class DeterministicQualificationAgent: + def __init__(self) -> None: + self._process_callback = None + + def set_process_callback(self, callback: object) -> None: + self._process_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") + if not ready or not Path(ready).with_suffix(Path(ready).suffix + ".enable").is_file(): + return + ready_path = Path(ready) + ready_path.write_text(json.dumps({"run_id": getattr(state, "run_id", None), "phase": "EXECUTE_AGENT"}), encoding="utf-8") + continue_path = ready_path.with_suffix(ready_path.suffix + ".continue") + deadline = time.monotonic() + 15 + while time.monotonic() < deadline: + if continue_path.is_file(): + return + time.sleep(.02) + raise RuntimeError("QUALIFICATION_CONTROL_ARM_TIMED_OUT") + def invoke(self, root: Path, prompt: str) -> AgentResult: + if callable(self._process_callback): + self._process_callback({"pid": os.getpid(), "process_group": os.getpgrp()}) if "sole automatic post-finalization reconciliation" in prompt.lower(): sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha=sha) diff --git a/tests/engineering/test_coverage_runtime_boundaries.py b/tests/engineering/test_coverage_runtime_boundaries.py index 410198d9..aa97fa7d 100644 --- a/tests/engineering/test_coverage_runtime_boundaries.py +++ b/tests/engineering/test_coverage_runtime_boundaries.py @@ -1223,18 +1223,18 @@ def test_local_api_service_is_retired_without_affecting_contract_fixtures(self) def test_controlled_provider_interruption_is_run_bound_single_use_and_redacted(self) -> None: root = self.root.parent / "recovery-control" - state = SimpleNamespace(terminal=False, phase="EXECUTE_AGENT") + state = SimpleNamespace(terminal=False, phase="INITIALIZE") with patch("engineering_platform.provider_recovery.StateStore") as states: states.return_value.load.return_value = state - armed = provider_recovery.arm_controlled_interruption(root, run_id="run-a", phase="QUALITY_CONTROL_AGENT", armed_by="user\nsecret", reason="reason\nsecret") + armed = provider_recovery.arm_controlled_interruption(root, run_id="run-a", phase="EXECUTE_AGENT", armed_by="user\nsecret", reason="reason\nsecret") self.assertEqual(armed["state"], "ARMED") - self.assertEqual(provider_recovery.controlled_interruption_status(root, run_id="run-a", phase="QUALITY_CONTROL_AGENT"), "ARMED") - self.assertEqual(provider_recovery.disarm_controlled_interruption(root, run_id="run-a", phase="QUALITY_CONTROL_AGENT"), "DISARMED") - self.assertEqual(provider_recovery.disarm_controlled_interruption(root, run_id="run-a", phase="QUALITY_CONTROL_AGENT"), "NOT_ARMED") + self.assertEqual(provider_recovery.controlled_interruption_status(root, run_id="run-a", phase="EXECUTE_AGENT"), "ARMED") + self.assertEqual(provider_recovery.disarm_controlled_interruption(root, run_id="run-a", phase="EXECUTE_AGENT"), "DISARMED") + self.assertEqual(provider_recovery.disarm_controlled_interruption(root, run_id="run-a", phase="EXECUTE_AGENT"), "NOT_ARMED") with patch("engineering_platform.provider_recovery.StateStore") as states: states.return_value.load.return_value = SimpleNamespace(terminal=True, phase="EXECUTE_AGENT") with self.assertRaisesRegex(provider_recovery.ControlledInterruptionControlError, "terminal"): - provider_recovery.arm_controlled_interruption(root, run_id="run-b", phase="QUALITY_CONTROL_AGENT") + provider_recovery.arm_controlled_interruption(root, run_id="run-b", phase="EXECUTE_AGENT") def test_recovery_reconciliation_fails_closed_for_absent_or_ambiguous_receipts(self) -> None: with patch("engineering_platform.provider_recovery.load_recovery_state", return_value=None): @@ -1284,18 +1284,18 @@ def test_provider_recovery_watcher_and_control_cli_never_create_unowned_work(sel "engineering_platform.provider_recovery.controlled_interruption_status", return_value="ARMED"), patch( "engineering_platform.provider_recovery.disarm_controlled_interruption", return_value="DISARMED" ), redirect_stdout(output): - self.assertEqual(provider_recovery.main(["arm-controlled-interruption", "--repo", str(self.root), "--run-id", "run-a", "--phase", "QUALITY_CONTROL_AGENT"]), 0) - self.assertEqual(provider_recovery.main(["controlled-interruption-status", "--repo", str(self.root), "--run-id", "run-a", "--phase", "QUALITY_CONTROL_AGENT"]), 0) - self.assertEqual(provider_recovery.main(["disarm-controlled-interruption", "--repo", str(self.root), "--run-id", "run-a", "--phase", "QUALITY_CONTROL_AGENT"]), 0) + self.assertEqual(provider_recovery.main(["arm-controlled-interruption", "--repo", str(self.root), "--run-id", "run-a", "--phase", "EXECUTE_AGENT"]), 0) + self.assertEqual(provider_recovery.main(["controlled-interruption-status", "--repo", str(self.root), "--run-id", "run-a", "--phase", "EXECUTE_AGENT"]), 0) + self.assertEqual(provider_recovery.main(["disarm-controlled-interruption", "--repo", str(self.root), "--run-id", "run-a", "--phase", "EXECUTE_AGENT"]), 0) self.assertIn("DISARMED", output.getvalue()) def test_controlled_interruption_hook_consumes_once_and_records_artifact(self) -> None: - with patch.dict("os.environ", {"ENGINEERING_PLATFORM_TEST_INTERRUPT_PROVIDER_ONCE": "run-a:QUALITY_CONTROL_AGENT"}, clear=False), patch( + with patch.dict("os.environ", {"ENGINEERING_PLATFORM_TEST_INTERRUPT_PROVIDER_ONCE": "run-a:EXECUTE_AGENT"}, clear=False), patch( "engineering_platform.provider_recovery.load_recovery_state", return_value=None ), patch("engineering_platform.provider_recovery.record_artifact") as recorded: - self.assertTrue(provider_recovery.consume_controlled_interruption_hook(self.root, run_id="run-a", phase="QUALITY_CONTROL_AGENT")) + self.assertTrue(provider_recovery.consume_controlled_interruption_hook(self.root, run_id="run-a", phase="EXECUTE_AGENT")) self.assertTrue(recorded.called) - self.assertFalse(provider_recovery.consume_controlled_interruption_hook(self.root, run_id="run-a", phase="QUALITY_CONTROL_AGENT")) + self.assertFalse(provider_recovery.consume_controlled_interruption_hook(self.root, run_id="run-a", phase="EXECUTE_AGENT")) def test_provider_recovery_transition_is_compare_and_swap_and_rejects_unknown_states(self) -> None: with self.assertRaises(ValueError): diff --git a/tests/engineering/test_provider_recovery_controller.py b/tests/engineering/test_provider_recovery_controller.py index 3aed0710..6a96a3dd 100644 --- a/tests/engineering/test_provider_recovery_controller.py +++ b/tests/engineering/test_provider_recovery_controller.py @@ -177,34 +177,34 @@ def test_controlled_hook_is_consumed_before_the_synthetic_interruption(self) -> def test_operator_arm_status_disarm_and_durable_consumption(self) -> None: run_id = "operator-control-run" self.store.save(TransactionState(run_id, "pcvantol/djconnect", "prompt.md", "LOCAL_REPOSITORY_VALIDATION")) - self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT"), "NOT_ARMED") - armed = arm_controlled_interruption(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT", armed_by="operator") + self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="EXECUTE_AGENT"), "NOT_ARMED") + armed = arm_controlled_interruption(self.root, run_id=run_id, phase="EXECUTE_AGENT", armed_by="operator") self.assertEqual(armed["state"], "ARMED") - self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT"), "ARMED") - self.assertTrue(consume_controlled_interruption_hook(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT")) - self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT"), "CONSUMED") - self.assertFalse(consume_controlled_interruption_hook(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT")) + self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="EXECUTE_AGENT"), "ARMED") + self.assertTrue(consume_controlled_interruption_hook(self.root, run_id=run_id, phase="EXECUTE_AGENT")) + self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="EXECUTE_AGENT"), "CONSUMED") + self.assertFalse(consume_controlled_interruption_hook(self.root, run_id=run_id, phase="EXECUTE_AGENT")) with self.assertRaises(ControlledInterruptionControlError): - disarm_controlled_interruption(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT") + disarm_controlled_interruption(self.root, run_id=run_id, phase="EXECUTE_AGENT") def test_operator_control_rejects_unsafe_targets(self) -> None: with self.assertRaises(ControlledInterruptionControlError): - arm_controlled_interruption(self.root, run_id="unknown-run", phase="QUALITY_CONTROL_AGENT") + arm_controlled_interruption(self.root, run_id="unknown-run", phase="EXECUTE_AGENT") with self.assertRaises(ControlledInterruptionControlError): arm_controlled_interruption(self.root, run_id=self.run_id, phase="EXECUTE_AGENT") self.store.save(TransactionState(self.run_id, "pcvantol/djconnect", "prompt.md", "QUALITY_CONTROL_AGENT")) with self.assertRaises(ControlledInterruptionControlError): - arm_controlled_interruption(self.root, run_id=self.run_id, phase="QUALITY_CONTROL_AGENT") + arm_controlled_interruption(self.root, run_id=self.run_id, phase="EXECUTE_AGENT") terminal_run = "terminal-operator-control-run" self.store.save(TransactionState(terminal_run, "pcvantol/djconnect", "prompt.md", "COMPLETE", terminal=True)) with self.assertRaises(ControlledInterruptionControlError): - arm_controlled_interruption(self.root, run_id=terminal_run, phase="QUALITY_CONTROL_AGENT") + arm_controlled_interruption(self.root, run_id=terminal_run, phase="EXECUTE_AGENT") def test_operator_disarm_preserves_unconsumed_and_wrong_run_cannot_consume(self) -> None: run_id = "operator-disarm-run" self.store.save(TransactionState(run_id, "pcvantol/djconnect", "prompt.md", "LOCAL_REPOSITORY_VALIDATION")) - arm_controlled_interruption(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT") - self.assertFalse(consume_controlled_interruption_hook(self.root, run_id="unrelated-run", phase="QUALITY_CONTROL_AGENT")) + arm_controlled_interruption(self.root, run_id=run_id, phase="EXECUTE_AGENT") + self.assertFalse(consume_controlled_interruption_hook(self.root, run_id="unrelated-run", phase="EXECUTE_AGENT")) self.assertFalse(consume_controlled_interruption_hook(self.root, run_id=run_id, phase="REPAIR_AGENT")) - self.assertEqual(disarm_controlled_interruption(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT"), "DISARMED") - self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="QUALITY_CONTROL_AGENT"), "NOT_ARMED") + self.assertEqual(disarm_controlled_interruption(self.root, run_id=run_id, phase="EXECUTE_AGENT"), "DISARMED") + self.assertEqual(controlled_interruption_status(self.root, run_id=run_id, phase="EXECUTE_AGENT"), "NOT_ARMED") diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 11127f04..6e6bf3cd 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Installed CENTRAL execution qualification for Genesis and Managed. +"""Installed CENTRAL execution qualification for Genesis, Managed and recovery. The default Managed fixture uses a local bare Git remote. Pass ``--managed-repository`` with a clean checkout of the explicitly approved @@ -87,6 +87,24 @@ def verify_receipt(data_root: Path, project: str, run_id: str) -> dict[str, obje return {"run_id": run_id, "assurance_reviews": observed, "terminal_phase": row[0]} +def verify_controlled_recovery(data_root: Path, checkout: Path, run_id: str, base: str) -> dict[str, object]: + control = checkout / ".engineering" / "artifacts" / "provider-recovery-fault-injection" / f"{run_id}-EXECUTE_AGENT.json" + consumed = json.loads(control.read_text(encoding="utf-8")) + if consumed.get("kind") != "CONTROLLED_PROVIDER_INTERRUPTION" or consumed.get("phase") != "EXECUTE_AGENT": + raise RuntimeError(f"CONTROLLED_INTERRUPTION_EVIDENCE_INVALID: {consumed}") + with sqlite3.connect(data_root / CENTRAL_DATABASE_FILENAME) as connection: + recovery = connection.execute( + "SELECT lifecycle_phase,state,result FROM provider_recovery_attempts WHERE run_id=?", (run_id,) + ).fetchone() + if recovery != ("EXECUTE_AGENT", "RECOVERED", "SUCCESS"): + raise RuntimeError(f"CONTROLLED_RECOVERY_LINEAGE_INVALID: {recovery}") + with urlopen(base + "/api/prompt-history?project=recovery", timeout=5) as response: # nosec B310 + history = json.loads(response.read()) + if not isinstance(history, list) or not any(item.get("run_id") == run_id for item in history if isinstance(item, dict)): + raise RuntimeError("CONTROLLED_RECOVERY_DASHBOARD_HISTORY_UNAVAILABLE") + return {"run_id": run_id, "control": "CONSUMED", "recovery": "RECOVERED", "dashboard_history": "VISIBLE"} + + def submit(base: str, credential: str, project: str, repository: str, prompt: str, key: str) -> str: body = {"repository_id": repository, "producer": {"id": "installed-e2e", "type": "HUMAN", "version": "1"}, "prompt": prompt, "idempotency_key": key, "constraints": {"mode": "GENESIS" if "Genesis" in prompt else "MANAGED"}} request = Request(base + f"/v1/projects/{project}/submissions", data=json.dumps(body).encode(), method="POST", headers={"Content-Type": "application/json", "Authorization": f"Bearer {credential}"}) @@ -133,10 +151,12 @@ def main(argv: list[str] | None = None) -> int: if project == "managed" and args.managed_repository: raise RuntimeError("MANAGED_GITHUB_FIXTURE_NEEDS_MATCHING_DECLARATION") command(server, "bind-repository", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + control_ready = root / "controlled-recovery-ready.json" env = { **os.environ, "EP_QUALIFICATION_DETERMINISTIC_FLOW": "1", "EP_CENTRAL_OPERATIONAL_DATABASE": str(data / CENTRAL_DATABASE_FILENAME), + "EP_QUALIFICATION_CONTROL_ARM_READY_FILE": str(control_ready), } process = subprocess.Popen((str(server), "serve", "--data-root", str(data)), env=env) # nosec B603 try: @@ -152,6 +172,33 @@ def main(argv: list[str] | None = None) -> int: submission = submit(base, credential, project, repository, prompt, f"{mode}-e2e") _, run_id = wait_terminal(server, data, submission) evidence[mode] = verify_receipt(data, project, run_id) + recovery = root / "controlled-recovery" + create_repository(recovery, origin=root / "controlled-recovery-origin.git") + command(server, "bootstrap-topology", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo") + command(server, "provision-declaration", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo", "--path", str(recovery)) + git(recovery, "add", ".engineering-platform") + git(recovery, "commit", "-qm", "bind recovery qualification project") + git(recovery, "push", "-q", "origin", "main") + command(server, "bind-repository", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo", "--path", str(recovery)) + control_ready.with_suffix(control_ready.suffix + ".enable").write_text("enabled\n", encoding="utf-8") + credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", "recovery", "--consumer-id", "recovery-e2e")["credential"]) + submission = submit(base, credential, "recovery", "recovery-repo", "Execution Mode: Managed\n\nInstalled controlled recovery qualification.", "controlled-recovery-e2e") + deadline = time.monotonic() + 30 + while not control_ready.is_file() and time.monotonic() < deadline: + time.sleep(.05) + if not control_ready.is_file(): + raise RuntimeError("CONTROLLED_RECOVERY_ARM_WINDOW_UNAVAILABLE") + control = json.loads(control_ready.read_text(encoding="utf-8")) + run_id = control.get("run_id") + if not isinstance(run_id, str) or control.get("phase") != "EXECUTE_AGENT": + raise RuntimeError(f"CONTROLLED_RECOVERY_ARM_WINDOW_INVALID: {control}") + subprocess.run((str(venv / "bin" / "python"), "-m", "engineering_platform.provider_recovery", "arm-controlled-interruption", "--repo", str(recovery), "--run-id", run_id, "--phase", "EXECUTE_AGENT", "--central-database", str(data / CENTRAL_DATABASE_FILENAME)), check=True, capture_output=True, text=True) # nosec B603 + control_ready.with_suffix(control_ready.suffix + ".continue").write_text("armed\n", encoding="utf-8") + _, terminal_run_id = wait_terminal(server, data, submission) + if terminal_run_id != run_id: + raise RuntimeError("CONTROLLED_RECOVERY_RUN_ID_CHANGED") + verify_receipt(data, "recovery", run_id) + evidence["controlled_recovery"] = verify_controlled_recovery(data, recovery, run_id, base) finally: process.terminate(); process.wait(timeout=10) print(json.dumps({"result": "PASS", "managed_fixture": "github" if args.managed_repository else "local-origin", "evidence": evidence}, sort_keys=True)) From b4fa7ad8866ca17564fc997b6cd09b3221a7361b Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:15:18 +0200 Subject: [PATCH 35/87] refactor: clarify deterministic e2e setup --- .../p_deterministic_execution_e2e.py | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 6e6bf3cd..8b68202d 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -112,6 +112,39 @@ def submit(base: str, credential: str, project: str, repository: str, prompt: st return str(json.loads(response.read())["submission_id"]) +def bind_project(server: Path, data_root: Path, *, project: str, repository: str, + checkout: Path, push_declaration: bool = False) -> None: + """Bind a clean fixture through the same installed Server commands as production.""" + command(server, "bootstrap-topology", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository) + command(server, "provision-declaration", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + git(checkout, "add", ".engineering-platform") + git(checkout, "commit", "-qm", "bind installed e2e project") + if push_declaration: + git(checkout, "push", "-q", "origin", "main") + command(server, "bind-repository", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + + +def arm_controlled_recovery(venv: Path, data_root: Path, checkout: Path, ready: Path) -> str: + """Wait for the deterministic adapter's bounded arm window, then use its public CLI.""" + deadline = time.monotonic() + 30 + while not ready.is_file() and time.monotonic() < deadline: + time.sleep(.05) + if not ready.is_file(): + raise RuntimeError("CONTROLLED_RECOVERY_ARM_WINDOW_UNAVAILABLE") + control = json.loads(ready.read_text(encoding="utf-8")) + run_id = control.get("run_id") + if not isinstance(run_id, str) or control.get("phase") != "EXECUTE_AGENT": + raise RuntimeError(f"CONTROLLED_RECOVERY_ARM_WINDOW_INVALID: {control}") + subprocess.run( + (str(venv / "bin" / "python"), "-m", "engineering_platform.provider_recovery", + "arm-controlled-interruption", "--repo", str(checkout), "--run-id", run_id, + "--phase", "EXECUTE_AGENT", "--central-database", str(data_root / CENTRAL_DATABASE_FILENAME)), + check=True, capture_output=True, text=True, + ) # nosec B603 + ready.with_suffix(ready.suffix + ".continue").write_text("armed\n", encoding="utf-8") + return run_id + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--source-root", type=Path, default=Path.cwd()) @@ -142,15 +175,9 @@ def main(argv: list[str] | None = None) -> int: evidence: dict[str, object] = {} layouts = (("genesis", "genesis-repo", genesis_host), ("managed", "managed-repo", managed)) for project, repository, checkout in layouts: - command(server, "bootstrap-topology", "--data-root", str(data), "--project-id", project, "--repository-id", repository) - command(server, "provision-declaration", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) - git(checkout, "add", ".engineering-platform") - git(checkout, "commit", "-qm", "bind installed e2e project") - if project == "managed": - git(checkout, "push", "-q", "origin", "main") + bind_project(server, data, project=project, repository=repository, checkout=checkout, push_declaration=project == "managed") if project == "managed" and args.managed_repository: raise RuntimeError("MANAGED_GITHUB_FIXTURE_NEEDS_MATCHING_DECLARATION") - command(server, "bind-repository", "--data-root", str(data), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) control_ready = root / "controlled-recovery-ready.json" env = { **os.environ, @@ -174,26 +201,11 @@ def main(argv: list[str] | None = None) -> int: evidence[mode] = verify_receipt(data, project, run_id) recovery = root / "controlled-recovery" create_repository(recovery, origin=root / "controlled-recovery-origin.git") - command(server, "bootstrap-topology", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo") - command(server, "provision-declaration", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo", "--path", str(recovery)) - git(recovery, "add", ".engineering-platform") - git(recovery, "commit", "-qm", "bind recovery qualification project") - git(recovery, "push", "-q", "origin", "main") - command(server, "bind-repository", "--data-root", str(data), "--project-id", "recovery", "--repository-id", "recovery-repo", "--path", str(recovery)) + bind_project(server, data, project="recovery", repository="recovery-repo", checkout=recovery, push_declaration=True) control_ready.with_suffix(control_ready.suffix + ".enable").write_text("enabled\n", encoding="utf-8") credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", "recovery", "--consumer-id", "recovery-e2e")["credential"]) submission = submit(base, credential, "recovery", "recovery-repo", "Execution Mode: Managed\n\nInstalled controlled recovery qualification.", "controlled-recovery-e2e") - deadline = time.monotonic() + 30 - while not control_ready.is_file() and time.monotonic() < deadline: - time.sleep(.05) - if not control_ready.is_file(): - raise RuntimeError("CONTROLLED_RECOVERY_ARM_WINDOW_UNAVAILABLE") - control = json.loads(control_ready.read_text(encoding="utf-8")) - run_id = control.get("run_id") - if not isinstance(run_id, str) or control.get("phase") != "EXECUTE_AGENT": - raise RuntimeError(f"CONTROLLED_RECOVERY_ARM_WINDOW_INVALID: {control}") - subprocess.run((str(venv / "bin" / "python"), "-m", "engineering_platform.provider_recovery", "arm-controlled-interruption", "--repo", str(recovery), "--run-id", run_id, "--phase", "EXECUTE_AGENT", "--central-database", str(data / CENTRAL_DATABASE_FILENAME)), check=True, capture_output=True, text=True) # nosec B603 - control_ready.with_suffix(control_ready.suffix + ".continue").write_text("armed\n", encoding="utf-8") + run_id = arm_controlled_recovery(venv, data, recovery, control_ready) _, terminal_run_id = wait_terminal(server, data, submission) if terminal_run_id != run_id: raise RuntimeError("CONTROLLED_RECOVERY_RUN_ID_CHANGED") From 3fee54410e7ce9494c40b765a19debe8734c294c Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:16:16 +0200 Subject: [PATCH 36/87] fix: translate central queue actions in all locales --- .../assets/dashboard_locales.mjs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/engineering_platform/assets/dashboard_locales.mjs b/src/engineering_platform/assets/dashboard_locales.mjs index 71b11e4f..99d0427f 100644 --- a/src/engineering_platform/assets/dashboard_locales.mjs +++ b/src/engineering_platform/assets/dashboard_locales.mjs @@ -1667,6 +1667,15 @@ export const DASHBOARD_MESSAGES = { "queue.defer_description": "{title} aus der aktiven Warteschlange verschieben? Die Datei bleibt in Inbox/_deferred erhalten und wird erst nach einer manuellen Rückgabe ausgeführt.", "queue.defer_failed": "Der Inbox-Auftrag konnte nicht sicher zurückgestellt werden.", "queue.defer_title": "Ausführung zurückstellen", + "queue.resume_action": "Fortsetzen", + "queue.resume_description": "{title} wieder in die aktive Warteschlange stellen? Die Ausführung kann beginnen, sobald sie an der Reihe ist.", + "queue.resume_title": "Ausführung fortsetzen", + "queue.quarantine_action": "In Quarantäne verschieben", + "queue.quarantine_description": "{title} in Quarantäne verschieben? Die Übermittlung bleibt erhalten und kann erst nach einer Operatorfreigabe ausgeführt werden.", + "queue.quarantine_title": "Ausführung in Quarantäne verschieben", + "queue.decline_action": "Ablehnen", + "queue.decline_description": "{title} ablehnen? Dadurch wird die Übermittlung geschlossen und kann nicht mehr aus der Warteschlange fortgesetzt werden.", + "queue.decline_title": "Ausführung ablehnen", "queue.filename": "Dateiname: {filename} · geändert: {modified}", "queue.runtime_invocation_blocked": "Die Inbox wartet, weil die lokale Codex-CLI nicht starten kann. Manuelle Reparatur: npm install -g @openai/codex@latest", "queue.managed_branch_blocked": "Die Inbox ist pausiert, weil dieser Arbeitsbereich auf einem Arbeitsbranch steht. Der Execution Host darf Arbeit nur von main beanspruchen.", @@ -2238,6 +2247,15 @@ export const DASHBOARD_MESSAGES = { "queue.defer_description": "Retirer {title} de la file active ? Le fichier reste conservé dans Inbox/_deferred et ne sera exécuté qu’après un retour manuel.", "queue.defer_failed": "L’élément Inbox n’a pas pu être reporté en toute sécurité.", "queue.defer_title": "Reporter l’exécution", + "queue.resume_action": "Reprendre", + "queue.resume_description": "Remettre {title} dans la file active ? L’exécution pourra démarrer lorsqu’elle arrivera en tête.", + "queue.resume_title": "Reprendre l’exécution", + "queue.quarantine_action": "Mettre en quarantaine", + "queue.quarantine_description": "Mettre {title} en quarantaine ? La soumission reste conservée et ne pourra être exécutée qu’après reprise par un opérateur.", + "queue.quarantine_title": "Mettre l’exécution en quarantaine", + "queue.decline_action": "Refuser", + "queue.decline_description": "Refuser {title} ? La soumission sera clôturée et ne pourra plus être reprise depuis la file.", + "queue.decline_title": "Refuser l’exécution", "queue.filename": "Nom du fichier : {filename} · modifié : {modified}", "queue.runtime_invocation_blocked": "La boîte de réception attend car l’interface CLI Codex locale ne peut pas démarrer. Réparation manuelle : npm install -g @openai/codex@latest", "queue.managed_branch_blocked": "La boîte de réception est en pause car cet espace de travail est sur une branche de travail. L’Execution Host ne peut réclamer du travail que depuis main.", @@ -2809,6 +2827,15 @@ export const DASHBOARD_MESSAGES = { "queue.defer_description": "¿Quitar {title} de la cola activa? El archivo se conserva en Inbox/_deferred y no se ejecutará hasta que se devuelva manualmente.", "queue.defer_failed": "El elemento de Inbox no se pudo aplazar de forma segura.", "queue.defer_title": "Aplazar ejecución", + "queue.resume_action": "Reanudar", + "queue.resume_description": "¿Devolver {title} a la cola activa? La ejecución podrá comenzar cuando llegue al primer puesto.", + "queue.resume_title": "Reanudar ejecución", + "queue.quarantine_action": "Poner en cuarentena", + "queue.quarantine_description": "¿Poner {title} en cuarentena? El envío se conserva y solo podrá ejecutarse tras reanudarlo un operador.", + "queue.quarantine_title": "Poner la ejecución en cuarentena", + "queue.decline_action": "Rechazar", + "queue.decline_description": "¿Rechazar {title}? El envío se cerrará y no podrá reanudarse desde la cola.", + "queue.decline_title": "Rechazar ejecución", "queue.filename": "Nombre de archivo: {filename} · modificado: {modified}", "queue.runtime_invocation_blocked": "La bandeja de entrada está esperando porque la CLI local de Codex no puede iniciarse. Reparación manual: npm install -g @openai/codex@latest", "queue.managed_branch_blocked": "La bandeja de entrada está en pausa porque este espacio de trabajo está en una rama de trabajo. El Execution Host solo puede reclamar trabajo desde main.", From ee3723b107e7421d33a6ed8d0b66304195dca973 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 00:17:22 +0200 Subject: [PATCH 37/87] fix: apply destructive queue action styling --- src/engineering_platform/assets/dashboard.css | 2 +- src/engineering_platform/assets/dashboard.js | 4 +++- tests/engineering/dashboard.spec.mjs | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.css b/src/engineering_platform/assets/dashboard.css index ee16e081..a67ad209 100644 --- a/src/engineering_platform/assets/dashboard.css +++ b/src/engineering_platform/assets/dashboard.css @@ -871,7 +871,7 @@ html:not([data-theme="light"]) .dashboard-modal-shell{--modal-header-surface:col .predecessor-retry::before{content:"↻";font:700 17px/1 system-ui;margin-right:8px;vertical-align:-1px} .execution-dismiss::before{content:"⊘"} #componentLogs .log-table{table-layout:auto;width:100%}#componentLogs .log-table th:last-child,#componentLogs .log-table td:last-child{width:100%} -#queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-item__actions{align-items:flex-end;display:flex;flex-direction:column;gap:6px;justify-self:end}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important} +#queueList .queue-item{grid-template-columns:1.25rem minmax(0,1fr) auto}.queue-item__actions{align-items:flex-end;display:flex;flex-direction:column;gap:6px;justify-self:end}.queue-defer{background:#3b281b;border:1px solid #f0b66a;border-radius:8px;color:#fff0dc;font:600 12px system-ui;min-height:32px;padding:5px 9px}.queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}.queue-defer:disabled{cursor:wait;opacity:.7}.queue-defer--destructive{background:#3a2028;border-color:#ff718f;color:#ffd9e1}.queue-defer--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important}html[data-theme="light"] .queue-defer{background:#fff8ef;border-color:#d68b23;color:#643a13}html[data-theme="light"] .queue-defer:hover:not(:disabled){background:var(--house-style)!important;border-color:var(--house-style)!important;color:#201812!important}html[data-theme="light"] .queue-defer--destructive{background:#fff1f4;border-color:#ff718f;color:#b32649}html[data-theme="light"] .queue-defer--destructive:hover:not(:disabled){background:#ff718f!important;border-color:#ff718f!important;color:#23131a!important} /* Shared semantic action variants prevent individual surfaces from drifting. */ .dashboard-action{align-items:center;border:1px solid;border-radius:50%;box-sizing:border-box;display:inline-flex;font:18px/1 system-ui;height:32px;justify-content:center;min-height:32px;min-width:32px;padding:0;width:32px} diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 853c7635..c14a61a7 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -961,9 +961,11 @@ function queueItems(x, queueDepth) { [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."], ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]].forEach(([disposition, actionKey, reason]) => { const action = document.createElement("button"); - action.className = "queue-defer"; + action.className = `queue-defer${disposition === "DECLINED" ? " queue-defer--destructive" : ""}`; action.type = "button"; action.textContent = t(`${actionKey}_action`); + action.title = t(`${actionKey}_action`); + action.setAttribute("aria-label", t(`${actionKey}_action`)); action.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); queueDisposition(item, disposition, reason, action); diff --git a/tests/engineering/dashboard.spec.mjs b/tests/engineering/dashboard.spec.mjs index 3b1c716d..c0d04e84 100644 --- a/tests/engineering/dashboard.spec.mjs +++ b/tests/engineering/dashboard.spec.mjs @@ -10144,6 +10144,10 @@ test.describe("Engineering Status browser smoke", () => { await page.locator("#confirmationModalCancel").click(); } await expect(page.locator("#queueList .queue-item__actions .queue-defer")).toHaveCount(3); + const decline = page.getByRole("button", { name: messages["queue.decline_action"], exact: true }); + await expect(decline).toHaveClass(/queue-defer--destructive/); + await expect(decline).toHaveAttribute("title", messages["queue.decline_action"]); + await expect(decline).toHaveAttribute("aria-label", messages["queue.decline_action"]); }); test("keeps a waiting Inbox item when deferring is cancelled", async ({ page }) => { From e52dfef2d610b73819ca4af081bdcd319d63e952 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:17:41 +0200 Subject: [PATCH 38/87] test: preserve schema downgrade fixture --- tests/engineering/test_receipt_run_provenance.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/engineering/test_receipt_run_provenance.py b/tests/engineering/test_receipt_run_provenance.py index b70b4b36..5cbcd1b1 100644 --- a/tests/engineering/test_receipt_run_provenance.py +++ b/tests/engineering/test_receipt_run_provenance.py @@ -68,7 +68,11 @@ def _downgrade_to_schema_51(self) -> None: db.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51)))") db.execute("INSERT INTO ep_installations(instance_id,created_at,schema_version) SELECT instance_id,created_at,51 FROM ep_installations_schema52") db.execute("DROP TABLE ep_installations_schema52") - db.execute("DELETE FROM engineering_schema_migrations WHERE version IN (52,53)") + # Keep the canary a genuine schema-51 installation. Newer Server + # migrations may already have been applied by setUp; retaining one + # would make `_schema_version` report the current schema and skip + # the provenance upgrade this fixture is meant to exercise. + db.execute("DELETE FROM engineering_schema_migrations WHERE version >= 52") db.execute("UPDATE engineering_metadata SET value='51' WHERE key='installation.schema_version'") db.execute("PRAGMA legacy_alter_table=OFF") @@ -78,7 +82,10 @@ def test_schema_51_upgrade_backfills_every_verified_canonical_binding(self) -> N server.initialize(self.root) with sqlite3.connect(self.database) as db: self.assertEqual(db.execute("SELECT submission_id,run_id,project_id,repository_id,installation_id FROM ep_receipt_run_provenance ORDER BY submission_id").fetchall(), [("sub-a", "run-a", "project-a", "repo-a", self.installation), ("sub-b", "run-b", "project-b", "repo-b", self.installation)]) - self.assertEqual(db.execute("SELECT MAX(version) FROM engineering_schema_migrations").fetchone()[0], 53) + self.assertEqual( + db.execute("SELECT MAX(version) FROM engineering_schema_migrations").fetchone()[0], + server.SERVER_STORE_SCHEMA_VERSION, + ) def test_schema_51_upgrade_rejects_an_incomplete_or_conflicting_import(self) -> None: self._downgrade_to_schema_51() From 6731fa376e29921d2bb844cc5065f4f0c0b4bc56 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:24:35 +0200 Subject: [PATCH 39/87] test: qualify managed GitHub handoff explicitly --- docs/engineering/EXECUTION_HOST_OPERATIONS.md | 20 +++++ .../capability_preflight.py | 5 +- src/engineering_platform/execution_host.py | 5 +- .../parity_lifecycle_dispatcher.py | 9 ++- .../qualification_runtime.py | 42 ++++++++++ .../p_deterministic_execution_e2e.py | 80 +++++++++++++++---- 6 files changed, 144 insertions(+), 17 deletions(-) diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index 477fa0e5..b6f92c3e 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -159,6 +159,26 @@ same-run `RECOVERED` lineage, terminal assurance evidence, and the run in the dashboard's project-scoped history. The dedicated provider-recovery unit suite additionally qualifies unsafe and ambiguous recovery branches. +### Explicit external Managed GitHub qualification + +The default deterministic qualification never contacts GitHub. To prove the +Managed hand-off against an approved dummy repository, an operator must supply +all three values deliberately: + +```sh +python3 tools/qualification/p_deterministic_execution_e2e.py --source-root . \ + --managed-repository /absolute/path/to/clean-dummy-checkout \ + --managed-github-repository owner/approved-dummy-repository \ + --allow-managed-github-writes +``` + +The command rejects a dirty checkout, a non-matching `origin`, unavailable +GitHub access, or a missing explicit write flag. It commits and pushes one +unique `qualification/managed-e2e-*` branch, creates one open PR against +`main`, and verifies both remote branch and PR identity. It does **not** merge, +close, or delete that PR or branch: the normal human merge boundary remains in +force. This profile is intentionally excluded from CI and release gates. + ## CENTRAL project lanes CENTRAL retains a FIFO lane per project. It permits at most one active, diff --git a/src/engineering_platform/capability_preflight.py b/src/engineering_platform/capability_preflight.py index 3085a416..c6c406b8 100644 --- a/src/engineering_platform/capability_preflight.py +++ b/src/engineering_platform/capability_preflight.py @@ -121,7 +121,10 @@ def execute(root: Path, prompt: str, *, run_id: str | None = None) -> Capability # Installed deterministic qualification replaces GitHub with the local # adapter before any lifecycle work. Its admission must therefore test # the adapter composition, not require an unrelated live GitHub session. - qualification_local_github = os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + qualification_local_github = ( + os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + and os.environ.get("EP_QUALIFICATION_GITHUB_WRITE_FLOW") != "1" + ) required_providers = provider_readiness_failures( root, require_github=mode != "GENESIS" and not qualification_local_github, ) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index b22a67f5..0c9f482d 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -548,7 +548,10 @@ def _provider_readiness_gate( # adapter before the runner is created. It must therefore qualify the # Managed lifecycle against that adapter, rather than demand an # unrelated interactive GitHub session from the CI runner. - qualification_local_github = os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + qualification_local_github = ( + os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1" + and os.environ.get("EP_QUALIFICATION_GITHUB_WRITE_FLOW") != "1" + ) missing = provider_readiness_failures( self.root, require_github=require_github and not qualification_local_github, diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index 49feeca2..c9934410 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -193,10 +193,17 @@ def _default_runner(repository_root: Path, *, central_database: Path | None = No """Construct the installed historical runner without a watcher or Agent.""" if os.environ.get("EP_QUALIFICATION_DETERMINISTIC_FLOW") == "1": from .qualification_runtime import DeterministicQualificationAgent, LocalQualificationGitHub + github: object = LocalQualificationGitHub(repository_root) + if os.environ.get("EP_QUALIFICATION_GITHUB_WRITE_FLOW") == "1": + remote = GitProvider().execute(repository_root, "git", "remote", "get-url", "origin") + match = re.search(r"github\.com[/:]([^/]+/[^/]+?)(?:\.git)?$", remote.stdout.strip()) + if remote.returncode != 0 or match is None: + raise RunnerError("QUALIFICATION_GITHUB_REMOTE_REQUIRED") + github = GhCliClient(repository=match.group(1)) return EngineeringRunner( repository_root, StateStore(repository_root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=False), - SubprocessRepositoryClient(), LocalQualificationGitHub(repository_root), DeterministicQualificationAgent(), + SubprocessRepositoryClient(), github, DeterministicQualificationAgent(), ) remote = GitProvider().execute(repository_root, "git", "remote", "get-url", "origin") match = re.search(r"github\.com[/:]([^/]+/[^/]+?)(?:\.git)?$", remote.stdout.strip()) diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index f0d9d128..926619cb 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -52,9 +52,51 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: target_root = Path(target).resolve() sha = subprocess.run(("git", "-C", str(target_root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(target_root), commit_sha=sha) + if self._github_write_target(root): + return self._create_github_managed_handoff(root) sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) + @staticmethod + def _github_write_target(root: Path) -> bool: + """Limit the external seam to its exact approved origin only.""" + if os.environ.get("EP_QUALIFICATION_GITHUB_WRITE_FLOW") != "1": + return False + expected = os.environ.get("EP_QUALIFICATION_GITHUB_REPOSITORY", "") + remote = subprocess.run(("git", "-C", str(root), "remote", "get-url", "origin"), text=True, capture_output=True) # nosec B603 + if remote.returncode: + return False + value = remote.stdout.strip().removesuffix(".git") + return value.removeprefix("https://github.com/").removeprefix("git@github.com:") == expected + + @staticmethod + def _create_github_managed_handoff(root: Path) -> AgentResult: + """Create one bounded dummy-repository branch and GitHub PR. + + This seam is reachable only through the explicit external qualification + command. It intentionally stops at the normal human merge boundary: + the production lifecycle must not auto-merge merely because this is a + dummy repository. + """ + repository = os.environ.get("EP_QUALIFICATION_GITHUB_REPOSITORY", "") + branch = os.environ.get("EP_QUALIFICATION_GITHUB_BRANCH", "") + if not repository or not branch: + raise RuntimeError("QUALIFICATION_GITHUB_WRITE_CONFIGURATION_INVALID") + + def run(*args: str) -> str: + return subprocess.run(args, check=True, text=True, capture_output=True).stdout.strip() # nosec B603 + + run("git", "-C", str(root), "switch", "-c", branch) + proof = root / ".engineering-platform" / "managed-github-e2e-proof.json" + proof.write_text(json.dumps({"kind": "EP_MANAGED_GITHUB_E2E", "version": 1}, sort_keys=True) + "\n", encoding="utf-8") + run("git", "-C", str(root), "add", str(proof.relative_to(root))) + run("git", "-C", str(root), "commit", "-m", "test: record managed GitHub qualification handoff") + run("git", "-C", str(root), "push", "--set-upstream", "origin", branch) + run("gh", "pr", "create", "--repo", repository, "--head", branch, "--base", "main", "--title", "test: managed GitHub qualification", "--body", "Explicitly authorized Engineering Platform dummy-repository qualification.") + number = int(run("gh", "pr", "view", branch, "--repo", repository, "--json", "number", "--jq", ".number")) + sha = run("git", "-C", str(root), "rev-parse", "HEAD") + return AgentResult("COMPLETE", branch=branch, pull_request=number, commit_sha=sha) + def available(self) -> bool: return True # Keep the public provider-version contract valid so the normal installed # compatibility gate remains part of qualification. diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 8b68202d..60f62ad7 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 """Installed CENTRAL execution qualification for Genesis, Managed and recovery. -The default Managed fixture uses a local bare Git remote. Pass -``--managed-repository`` with a clean checkout of the explicitly approved -dummy GitHub repository to qualify the same flow against GitHub transport; -the runtime still uses :class:`LocalQualificationGitHub`, so it never creates -or mutates a GitHub pull request. +The default Managed fixture uses a local bare Git remote. An external GitHub +qualification is deliberately separate: it requires a clean checkout, its +exact approved ``owner/repository`` identity, and an explicit write flag. It +creates one branch and pull request, then stops at the normal human merge +boundary; CI never selects that profile. """ from __future__ import annotations @@ -19,6 +19,7 @@ import sys import tempfile import time +import uuid from urllib.request import Request, urlopen @@ -26,7 +27,7 @@ def command(binary: Path, *args: str) -> dict[str, object]: - result = subprocess.run((str(binary), *args), check=True, text=True, capture_output=True) # nosec B603 + result = subprocess.run((str(binary), "-m", "engineering_platform.server", *args), check=True, text=True, capture_output=True) # nosec B603 return json.loads(result.stdout) @@ -34,6 +35,23 @@ def git(path: Path, *args: str) -> None: subprocess.run(("git", "-C", str(path), *args), check=True, capture_output=True) # nosec B603 +def git_output(path: Path, *args: str) -> str: + return subprocess.run(("git", "-C", str(path), *args), check=True, text=True, capture_output=True).stdout.strip() # nosec B603 + + +def approved_github_checkout(path: Path, repository: str) -> Path: + """Fail closed unless the operator named this exact clean GitHub checkout.""" + checkout = path.resolve() + if not (checkout / ".git").exists() or git_output(checkout, "status", "--porcelain"): + raise RuntimeError("MANAGED_GITHUB_FIXTURE_MUST_BE_A_CLEAN_GIT_CHECKOUT") + remote = git_output(checkout, "remote", "get-url", "origin").removesuffix(".git") + normalized = remote.removeprefix("https://github.com/").removeprefix("git@github.com:") + if normalized != repository: + raise RuntimeError("MANAGED_GITHUB_FIXTURE_REPOSITORY_MISMATCH") + subprocess.run(("gh", "api", f"repos/{repository}"), check=True, capture_output=True) # nosec B603 + return checkout + + def port() -> int: with socket.socket() as listener: listener.bind(("127.0.0.1", 0)) @@ -70,6 +88,27 @@ def wait_terminal(server: Path, data_root: Path, submission_id: str) -> tuple[st raise RuntimeError(f"E2E_EXECUTION_TIMED_OUT: {submission_id}") +def wait_github_handoff(checkout: Path, repository: str, branch: str) -> dict[str, object]: + """Prove the external write boundary without bypassing human merge authority.""" + deadline = time.monotonic() + 120 + while time.monotonic() < deadline: + result = subprocess.run( + ("gh", "pr", "list", "--repo", repository, "--head", branch, "--state", "open", "--json", "number,url,headRefName,baseRefName"), + check=True, text=True, capture_output=True, + ) # nosec B603 + pull_requests = json.loads(result.stdout) + if isinstance(pull_requests, list) and len(pull_requests) == 1: + pull_request = pull_requests[0] + if pull_request.get("headRefName") != branch or pull_request.get("baseRefName") != "main": + raise RuntimeError("MANAGED_GITHUB_PULL_REQUEST_SCOPE_INVALID") + remote_branch = git_output(checkout, "ls-remote", "--heads", "origin", f"refs/heads/{branch}") + if not remote_branch: + raise RuntimeError("MANAGED_GITHUB_REMOTE_BRANCH_UNAVAILABLE") + return {"repository": repository, "branch": branch, "pull_request": pull_request} + time.sleep(.2) + raise RuntimeError("MANAGED_GITHUB_PULL_REQUEST_TIMED_OUT") + + def verify_receipt(data_root: Path, project: str, run_id: str) -> dict[str, object]: findings = data_root / "artifacts" / "projects" / project / "runs" / run_id / "assurance-findings-v1.json" receipt = json.loads(findings.read_text(encoding="utf-8")) @@ -149,14 +188,22 @@ def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--source-root", type=Path, default=Path.cwd()) parser.add_argument("--managed-repository", type=Path, help="Clean checkout of the approved dummy GitHub repository.") + parser.add_argument("--managed-github-repository", help="Exact approved dummy GitHub owner/repository identity.") + parser.add_argument("--allow-managed-github-writes", action="store_true", help="Explicitly authorize one dummy-repository branch and pull request.") args = parser.parse_args(argv) + github_write = args.allow_managed_github_writes or args.managed_github_repository is not None + if github_write and (not args.allow_managed_github_writes or not args.managed_repository or not args.managed_github_repository): + raise RuntimeError("MANAGED_GITHUB_WRITE_AUTHORIZATION_REQUIRED") with tempfile.TemporaryDirectory(prefix="ep-deterministic-e2e-") as temporary: root, wheelhouse, venv, data = Path(temporary), Path(temporary) / "wheelhouse", Path(temporary) / "venv", Path(temporary) / "central" 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, "-m", "venv", str(venv)), check=True) # nosec B603 subprocess.run((str(venv / "bin" / "pip"), "install", "--no-index", "--find-links", str(wheelhouse), "engineering-platform"), check=True, capture_output=True, text=True) # nosec B603 - server = venv / "bin" / "engineering-platform-server" + # Invoke the installed module directly so qualification verifies the + # wheel contents rather than relying on a platform-specific console + # script wrapper being present in the virtual environment. + server = venv / "bin" / "python" bind_port = port() command(server, "init", "--data-root", str(data), "--bind-port", str(bind_port)) genesis_host, genesis_target = root / "genesis-host", root / "genesis-target" @@ -166,9 +213,9 @@ def main(argv: list[str] | None = None) -> int: local.mkdir() (local / "engineering-platform.local.json").write_text(json.dumps({"workspace": {"workspace_authorization": {"allowed_roots": [], "allowed_repositories": [str(genesis_target.resolve())], "denied_repositories": [], "symlink_policy": "reject", "case_sensitivity": "host"}}}), encoding="utf-8") if args.managed_repository: - managed = args.managed_repository.resolve() - if not (managed / ".git").exists() or subprocess.run(("git", "-C", str(managed), "status", "--porcelain"), text=True, capture_output=True).stdout.strip(): - raise RuntimeError("MANAGED_GITHUB_FIXTURE_MUST_BE_A_CLEAN_GIT_CHECKOUT") + if not github_write: + raise RuntimeError("MANAGED_GITHUB_WRITE_AUTHORIZATION_REQUIRED") + managed = approved_github_checkout(args.managed_repository, str(args.managed_github_repository)) else: managed = root / "managed" create_repository(managed, origin=root / "managed-origin.git") @@ -176,8 +223,6 @@ def main(argv: list[str] | None = None) -> int: layouts = (("genesis", "genesis-repo", genesis_host), ("managed", "managed-repo", managed)) for project, repository, checkout in layouts: bind_project(server, data, project=project, repository=repository, checkout=checkout, push_declaration=project == "managed") - if project == "managed" and args.managed_repository: - raise RuntimeError("MANAGED_GITHUB_FIXTURE_NEEDS_MATCHING_DECLARATION") control_ready = root / "controlled-recovery-ready.json" env = { **os.environ, @@ -185,7 +230,11 @@ def main(argv: list[str] | None = None) -> int: "EP_CENTRAL_OPERATIONAL_DATABASE": str(data / CENTRAL_DATABASE_FILENAME), "EP_QUALIFICATION_CONTROL_ARM_READY_FILE": str(control_ready), } - process = subprocess.Popen((str(server), "serve", "--data-root", str(data)), env=env) # nosec B603 + github_branch = None + if github_write: + github_branch = f"qualification/managed-e2e-{uuid.uuid4().hex[:12]}" + env.update({"EP_QUALIFICATION_GITHUB_WRITE_FLOW": "1", "EP_QUALIFICATION_GITHUB_REPOSITORY": str(args.managed_github_repository), "EP_QUALIFICATION_GITHUB_BRANCH": github_branch}) + process = subprocess.Popen((str(server), "-m", "engineering_platform.server", "serve", "--data-root", str(data)), env=env) # nosec B603 try: base = f"http://127.0.0.1:{bind_port}" for _ in range(100): @@ -197,6 +246,9 @@ def main(argv: list[str] | None = None) -> int: for mode, project, repository, prompt in (("genesis", "genesis", "genesis-repo", f"Execution Mode: Genesis\nTarget repository: {genesis_target}\n\nInstalled deterministic qualification."), ("managed", "managed", "managed-repo", "Execution Mode: Managed\n\nInstalled deterministic qualification.")): credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", project, "--consumer-id", f"{mode}-e2e")["credential"]) submission = submit(base, credential, project, repository, prompt, f"{mode}-e2e") + if mode == "managed" and github_write: + evidence[mode] = wait_github_handoff(managed, str(args.managed_github_repository), str(github_branch)) + continue _, run_id = wait_terminal(server, data, submission) evidence[mode] = verify_receipt(data, project, run_id) recovery = root / "controlled-recovery" @@ -213,7 +265,7 @@ def main(argv: list[str] | None = None) -> int: evidence["controlled_recovery"] = verify_controlled_recovery(data, recovery, run_id, base) finally: process.terminate(); process.wait(timeout=10) - print(json.dumps({"result": "PASS", "managed_fixture": "github" if args.managed_repository else "local-origin", "evidence": evidence}, sort_keys=True)) + print(json.dumps({"result": "PASS", "managed_fixture": "github-write" if github_write else "local-origin", "evidence": evidence}, sort_keys=True)) return 0 From ac0f0e1a4e19a6195be7e69972ec22578556ad07 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:28:35 +0200 Subject: [PATCH 40/87] fix: support bound GitHub qualification fixture --- .../parity_lifecycle_dispatcher.py | 6 ++-- .../p_deterministic_execution_e2e.py | 36 +++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/engineering_platform/parity_lifecycle_dispatcher.py b/src/engineering_platform/parity_lifecycle_dispatcher.py index c9934410..52775145 100644 --- a/src/engineering_platform/parity_lifecycle_dispatcher.py +++ b/src/engineering_platform/parity_lifecycle_dispatcher.py @@ -197,9 +197,9 @@ def _default_runner(repository_root: Path, *, central_database: Path | None = No if os.environ.get("EP_QUALIFICATION_GITHUB_WRITE_FLOW") == "1": remote = GitProvider().execute(repository_root, "git", "remote", "get-url", "origin") match = re.search(r"github\.com[/:]([^/]+/[^/]+?)(?:\.git)?$", remote.stdout.strip()) - if remote.returncode != 0 or match is None: - raise RunnerError("QUALIFICATION_GITHUB_REMOTE_REQUIRED") - github = GhCliClient(repository=match.group(1)) + expected = os.environ.get("EP_QUALIFICATION_GITHUB_REPOSITORY") + if remote.returncode == 0 and match is not None and match.group(1) == expected: + github = GhCliClient(repository=match.group(1)) return EngineeringRunner( repository_root, StateStore(repository_root / ".engineering" / "engineering-runs", central_database=central_database, emit_local_projection=False), diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 60f62ad7..db0bc97a 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -52,6 +52,19 @@ def approved_github_checkout(path: Path, repository: str) -> Path: return checkout +def declared_fixture_identity(checkout: Path) -> tuple[str, str]: + """Use the fixture's committed binding; never overwrite its authority.""" + try: + declaration = json.loads((checkout / ".engineering-platform" / "repository.json").read_text(encoding="utf-8")) + project = declaration["project"]["id"] + repository = declaration["repository"]["id"] + except (OSError, KeyError, TypeError, json.JSONDecodeError) as error: + raise RuntimeError("MANAGED_GITHUB_FIXTURE_DECLARATION_REQUIRED") from error + if not isinstance(project, str) or not isinstance(repository, str) or not project or not repository: + raise RuntimeError("MANAGED_GITHUB_FIXTURE_DECLARATION_INVALID") + return project, repository + + def port() -> int: with socket.socket() as listener: listener.bind(("127.0.0.1", 0)) @@ -152,14 +165,15 @@ def submit(base: str, credential: str, project: str, repository: str, prompt: st def bind_project(server: Path, data_root: Path, *, project: str, repository: str, - checkout: Path, push_declaration: bool = False) -> None: + checkout: Path, push_declaration: bool = False, existing_declaration: bool = False) -> None: """Bind a clean fixture through the same installed Server commands as production.""" command(server, "bootstrap-topology", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository) - command(server, "provision-declaration", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) - git(checkout, "add", ".engineering-platform") - git(checkout, "commit", "-qm", "bind installed e2e project") - if push_declaration: - git(checkout, "push", "-q", "origin", "main") + if not existing_declaration: + command(server, "provision-declaration", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) + git(checkout, "add", ".engineering-platform") + git(checkout, "commit", "-qm", "bind installed e2e project") + if push_declaration: + git(checkout, "push", "-q", "origin", "main") command(server, "bind-repository", "--data-root", str(data_root), "--project-id", project, "--repository-id", repository, "--path", str(checkout)) @@ -216,13 +230,15 @@ def main(argv: list[str] | None = None) -> int: if not github_write: raise RuntimeError("MANAGED_GITHUB_WRITE_AUTHORIZATION_REQUIRED") managed = approved_github_checkout(args.managed_repository, str(args.managed_github_repository)) + managed_project, managed_identity = declared_fixture_identity(managed) else: managed = root / "managed" create_repository(managed, origin=root / "managed-origin.git") + managed_project, managed_identity = "managed", "managed-repo" evidence: dict[str, object] = {} - layouts = (("genesis", "genesis-repo", genesis_host), ("managed", "managed-repo", managed)) - for project, repository, checkout in layouts: - bind_project(server, data, project=project, repository=repository, checkout=checkout, push_declaration=project == "managed") + layouts = (("genesis", "genesis", "genesis-repo", genesis_host), ("managed", managed_project, managed_identity, managed)) + for mode, project, repository, checkout in layouts: + bind_project(server, data, project=project, repository=repository, checkout=checkout, push_declaration=mode == "managed", existing_declaration=github_write and mode == "managed") control_ready = root / "controlled-recovery-ready.json" env = { **os.environ, @@ -243,7 +259,7 @@ def main(argv: list[str] | None = None) -> int: break except OSError: time.sleep(.1) - for mode, project, repository, prompt in (("genesis", "genesis", "genesis-repo", f"Execution Mode: Genesis\nTarget repository: {genesis_target}\n\nInstalled deterministic qualification."), ("managed", "managed", "managed-repo", "Execution Mode: Managed\n\nInstalled deterministic qualification.")): + for mode, project, repository, prompt in (("genesis", "genesis", "genesis-repo", f"Execution Mode: Genesis\nTarget repository: {genesis_target}\n\nInstalled deterministic qualification."), ("managed", managed_project, managed_identity, "Execution Mode: Managed\n\nInstalled deterministic qualification.")): credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", project, "--consumer-id", f"{mode}-e2e")["credential"]) submission = submit(base, credential, project, repository, prompt, f"{mode}-e2e") if mode == "managed" and github_write: From bf50010398b1df48108a2ca1ac3bcbddb07ccc17 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:34:07 +0200 Subject: [PATCH 41/87] feat: retain external GitHub qualification runtime --- .../p_deterministic_execution_e2e.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index db0bc97a..907d0b51 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -10,6 +10,7 @@ from __future__ import annotations import argparse +from contextlib import nullcontext import json import os from pathlib import Path @@ -204,12 +205,20 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--managed-repository", type=Path, help="Clean checkout of the approved dummy GitHub repository.") parser.add_argument("--managed-github-repository", help="Exact approved dummy GitHub owner/repository identity.") parser.add_argument("--allow-managed-github-writes", action="store_true", help="Explicitly authorize one dummy-repository branch and pull request.") + parser.add_argument("--persistent-root", type=Path, help="New isolated qualification root retained after an external GitHub hand-off.") + parser.add_argument("--bind-port", type=int, help="Fixed localhost port for a retained qualification Server.") args = parser.parse_args(argv) github_write = args.allow_managed_github_writes or args.managed_github_repository is not None if github_write and (not args.allow_managed_github_writes or not args.managed_repository or not args.managed_github_repository): raise RuntimeError("MANAGED_GITHUB_WRITE_AUTHORIZATION_REQUIRED") - with tempfile.TemporaryDirectory(prefix="ep-deterministic-e2e-") as temporary: + if args.persistent_root and not github_write: + raise RuntimeError("PERSISTENT_QUALIFICATION_REQUIRES_GITHUB_WRITE_PROFILE") + if args.persistent_root and args.persistent_root.exists() and any(args.persistent_root.iterdir()): + raise RuntimeError("PERSISTENT_QUALIFICATION_ROOT_MUST_BE_EMPTY") + context = nullcontext(str(args.persistent_root.resolve())) if args.persistent_root else tempfile.TemporaryDirectory(prefix="ep-deterministic-e2e-") + with context as temporary: 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, "-m", "venv", str(venv)), check=True) # nosec B603 @@ -218,7 +227,7 @@ def main(argv: list[str] | None = None) -> int: # wheel contents rather than relying on a platform-specific console # script wrapper being present in the virtual environment. server = venv / "bin" / "python" - bind_port = port() + bind_port = args.bind_port or port() command(server, "init", "--data-root", str(data), "--bind-port", str(bind_port)) genesis_host, genesis_target = root / "genesis-host", root / "genesis-target" create_repository(genesis_host) @@ -280,7 +289,10 @@ def main(argv: list[str] | None = None) -> int: verify_receipt(data, "recovery", run_id) evidence["controlled_recovery"] = verify_controlled_recovery(data, recovery, run_id, base) finally: - process.terminate(); process.wait(timeout=10) + if not args.persistent_root: + process.terminate(); process.wait(timeout=10) + else: + (root / "qualification-runtime.json").write_text(json.dumps({"data_root": str(data), "port": bind_port, "pid": process.pid, "managed_project": managed_project}, sort_keys=True) + "\n", encoding="utf-8") print(json.dumps({"result": "PASS", "managed_fixture": "github-write" if github_write else "local-origin", "evidence": evidence}, sort_keys=True)) return 0 From f57ef08928d1fb1e0c841cf5dd4259ebd3064f31 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:36:25 +0200 Subject: [PATCH 42/87] fix: install qualification wheel explicitly --- tools/qualification/p_deterministic_execution_e2e.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 907d0b51..479a1ecb 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -222,7 +222,13 @@ def main(argv: list[str] | None = None) -> int: 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, "-m", "venv", str(venv)), check=True) # nosec B603 - subprocess.run((str(venv / "bin" / "pip"), "install", "--no-index", "--find-links", str(wheelhouse), "engineering-platform"), check=True, capture_output=True, text=True) # nosec B603 + wheels = tuple(wheelhouse.glob("engineering_platform-*.whl")) + if len(wheels) != 1: + raise RuntimeError("QUALIFICATION_WHEEL_UNAVAILABLE") + # Install the exact wheel path. A requirement-name install from the + # source checkout can falsely treat its adjacent metadata as already + # installed, leaving a non-restartable qualification venv. + subprocess.run((str(venv / "bin" / "pip"), "install", "--no-index", "--force-reinstall", str(wheels[0])), check=True, capture_output=True, text=True) # nosec B603 # Invoke the installed module directly so qualification verifies the # wheel contents rather than relying on a platform-specific console # script wrapper being present in the virtual environment. From 34ad56a7d1bdad50ee83a769f7f0fbbe99c86c56 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 07:41:02 +0200 Subject: [PATCH 43/87] fix: create distinct GitHub finalization qualification PR --- src/engineering_platform/qualification_runtime.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index 926619cb..1a175141 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -53,7 +53,7 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: sha = subprocess.run(("git", "-C", str(target_root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(target_root), commit_sha=sha) if self._github_write_target(root): - return self._create_github_managed_handoff(root) + return self._create_github_managed_handoff(root, finalization="finalization" in prompt.lower()) sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) @@ -70,7 +70,7 @@ def _github_write_target(root: Path) -> bool: return value.removeprefix("https://github.com/").removeprefix("git@github.com:") == expected @staticmethod - def _create_github_managed_handoff(root: Path) -> AgentResult: + def _create_github_managed_handoff(root: Path, *, finalization: bool = False) -> AgentResult: """Create one bounded dummy-repository branch and GitHub PR. This seam is reachable only through the explicit external qualification @@ -80,6 +80,8 @@ def _create_github_managed_handoff(root: Path) -> AgentResult: """ repository = os.environ.get("EP_QUALIFICATION_GITHUB_REPOSITORY", "") branch = os.environ.get("EP_QUALIFICATION_GITHUB_BRANCH", "") + if finalization: + branch = branch + "-finalization" if not repository or not branch: raise RuntimeError("QUALIFICATION_GITHUB_WRITE_CONFIGURATION_INVALID") @@ -87,12 +89,13 @@ def run(*args: str) -> str: return subprocess.run(args, check=True, text=True, capture_output=True).stdout.strip() # nosec B603 run("git", "-C", str(root), "switch", "-c", branch) - proof = root / ".engineering-platform" / "managed-github-e2e-proof.json" - proof.write_text(json.dumps({"kind": "EP_MANAGED_GITHUB_E2E", "version": 1}, sort_keys=True) + "\n", encoding="utf-8") + proof = root / ".engineering-platform" / ("managed-github-e2e-finalization-proof.json" if finalization else "managed-github-e2e-proof.json") + proof.write_text(json.dumps({"kind": "EP_MANAGED_GITHUB_E2E", "version": 1, "stage": "FINALIZATION" if finalization else "IMPLEMENTATION"}, sort_keys=True) + "\n", encoding="utf-8") run("git", "-C", str(root), "add", str(proof.relative_to(root))) run("git", "-C", str(root), "commit", "-m", "test: record managed GitHub qualification handoff") run("git", "-C", str(root), "push", "--set-upstream", "origin", branch) - run("gh", "pr", "create", "--repo", repository, "--head", branch, "--base", "main", "--title", "test: managed GitHub qualification", "--body", "Explicitly authorized Engineering Platform dummy-repository qualification.") + title = "test: managed GitHub qualification finalization" if finalization else "test: managed GitHub qualification" + run("gh", "pr", "create", "--repo", repository, "--head", branch, "--base", "main", "--title", title, "--body", "Explicitly authorized Engineering Platform dummy-repository qualification.") number = int(run("gh", "pr", "view", branch, "--repo", repository, "--json", "number", "--jq", ".number")) sha = run("git", "-C", str(root), "rev-parse", "HEAD") return AgentResult("COMPLETE", branch=branch, pull_request=number, commit_sha=sha) From a037904ec8b8eed6e04c0220e6095a23141e32dd Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:12:59 +0200 Subject: [PATCH 44/87] fix: complete GitHub qualification finalization --- docs/engineering/EXECUTION_HOST_OPERATIONS.md | 15 ++- src/engineering_platform/execution_host.py | 8 ++ .../qualification_runtime.py | 43 +++++++- tests/engineering/test_execution_host.py | 31 +++++- .../p_deterministic_execution_e2e.py | 101 +++++++++++++++--- 5 files changed, 170 insertions(+), 28 deletions(-) diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index b6f92c3e..cb87b1ea 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -173,11 +173,16 @@ python3 tools/qualification/p_deterministic_execution_e2e.py --source-root . \ ``` The command rejects a dirty checkout, a non-matching `origin`, unavailable -GitHub access, or a missing explicit write flag. It commits and pushes one -unique `qualification/managed-e2e-*` branch, creates one open PR against -`main`, and verifies both remote branch and PR identity. It does **not** merge, -close, or delete that PR or branch: the normal human merge boundary remains in -force. This profile is intentionally excluded from CI and release gates. +GitHub access, or a missing explicit write flag. It commits and pushes an +implementation branch, creates and verifies its PR, then acts as the explicit +operator only for that named disposable fixture: it merges the implementation +PR, verifies the host-created `codex/finalize-` Finalization PR, +merges it, and requires the canonical run to reach `COMPLETE`. It repeats the +same real remote handoff and finalization for the armed recovery lane, proving +the same-run `RECOVERED` lineage rather than merely an armed marker. Remote +branches are deleted by the GitHub merge operation; the fixture repository and +the optional persistent local qualification root are retained for inspection. +This profile is intentionally excluded from CI and release gates. ## CENTRAL project lanes diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 0c9f482d..e941d95e 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -2960,6 +2960,14 @@ def _advance_after_finalization_agent_result( "finalization_pr_required", result.diagnostic or "Finalization pull request was not created.", ) + expected_branch = finalization.finalization_branch or finalization.branch + if not expected_branch or result.branch != expected_branch: + return self._save_terminal( + finalization, + "BLOCKED", + "finalization_branch_mismatch", + "Finalization returned a pull request outside the durable Finalization branch.", + ) finalization = replace( finalization, phase="WAIT_FOR_TERMINAL_EVIDENCE", diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index 1a175141..9b67d455 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -8,6 +8,7 @@ from pathlib import Path import json import os +import re import subprocess import time @@ -52,10 +53,27 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: target_root = Path(target).resolve() sha = subprocess.run(("git", "-C", str(target_root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(target_root), commit_sha=sha) + is_finalization = "finalization pr on exactly" in prompt.lower() if self._github_write_target(root): - return self._create_github_managed_handoff(root, finalization="finalization" in prompt.lower()) + return self._create_github_managed_handoff( + root, + finalization=is_finalization, + prompt=prompt, + ) sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() - return AgentResult("COMPLETE", branch="qualification-managed", pull_request=1, commit_sha=sha) + branch = self._finalization_branch(prompt) if is_finalization else "qualification-managed" + return AgentResult("COMPLETE", branch=branch, pull_request=1, commit_sha=sha) + + @staticmethod + def _finalization_branch(prompt: str) -> str: + match = re.search( + r"finalization pr on exactly `([^`]+)`", + prompt, + flags=re.IGNORECASE, + ) + if not match: + raise RuntimeError("QUALIFICATION_FINALIZATION_BRANCH_UNAVAILABLE") + return match.group(1) @staticmethod def _github_write_target(root: Path) -> bool: @@ -70,7 +88,9 @@ def _github_write_target(root: Path) -> bool: return value.removeprefix("https://github.com/").removeprefix("git@github.com:") == expected @staticmethod - def _create_github_managed_handoff(root: Path, *, finalization: bool = False) -> AgentResult: + def _create_github_managed_handoff( + root: Path, *, finalization: bool = False, prompt: str = "" + ) -> AgentResult: """Create one bounded dummy-repository branch and GitHub PR. This seam is reachable only through the explicit external qualification @@ -81,7 +101,15 @@ def _create_github_managed_handoff(root: Path, *, finalization: bool = False) -> repository = os.environ.get("EP_QUALIFICATION_GITHUB_REPOSITORY", "") branch = os.environ.get("EP_QUALIFICATION_GITHUB_BRANCH", "") if finalization: - branch = branch + "-finalization" + # The host checkpoints the only permitted Finalization branch + # before it invokes a provider. The external qualification seam + # must exercise that contract exactly; inventing a fixture branch + # makes a successful remote PR unrecoverable by the host. + branch = DeterministicQualificationAgent._finalization_branch(prompt) + elif "controlled recovery qualification" in prompt.lower(): + # A second Managed transaction in the same fixture must retain a + # distinct remote handoff identity after the armed interruption. + branch = branch + "-recovery" if not repository or not branch: raise RuntimeError("QUALIFICATION_GITHUB_WRITE_CONFIGURATION_INVALID") @@ -90,7 +118,12 @@ def run(*args: str) -> str: run("git", "-C", str(root), "switch", "-c", branch) proof = root / ".engineering-platform" / ("managed-github-e2e-finalization-proof.json" if finalization else "managed-github-e2e-proof.json") - proof.write_text(json.dumps({"kind": "EP_MANAGED_GITHUB_E2E", "version": 1, "stage": "FINALIZATION" if finalization else "IMPLEMENTATION"}, sort_keys=True) + "\n", encoding="utf-8") + proof.write_text(json.dumps({ + "branch": branch, + "kind": "EP_MANAGED_GITHUB_E2E", + "stage": "FINALIZATION" if finalization else "IMPLEMENTATION", + "version": 1, + }, sort_keys=True) + "\n", encoding="utf-8") run("git", "-C", str(root), "add", str(proof.relative_to(root))) run("git", "-C", str(root), "commit", "-m", "test: record managed GitHub qualification handoff") run("git", "-C", str(root), "push", "--set-upstream", "origin", branch) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index f38125a5..6259be18 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -3496,7 +3496,7 @@ def test_merged_operator_handoff_resumes_on_a_later_poll(self) -> None: ]) runner = EngineeringRunner( self.root, self.store, FakeRepository(), github, - SequencedFakeAgent([AgentResult("WAITING", "codex/final", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]), lambda _: None, + SequencedFakeAgent([AgentResult("WAITING", "codex/finalize-later-merge", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]), lambda _: None, ) result = runner._poll(state) @@ -3509,7 +3509,7 @@ def test_merged_implementation_starts_and_reconciles_finalization(self) -> None: implementation = PullRequestEvidence(21, "MERGED", True, True, "b" * 40) final_open = PullRequestEvidence(22, "OPEN", True, True) final_merged = PullRequestEvidence(22, "MERGED", True, True, "c" * 40) - agent = SequencedFakeAgent([AgentResult("WAITING", "codex/final", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]) + agent = SequencedFakeAgent([AgentResult("WAITING", "codex/finalize-full-run", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]) state = TransactionState("full-run", "pcvantol/djconnect", str(self.prompt), "WAIT_FOR_TERMINAL_EVIDENCE", branch="codex/implementation", pull_request=21, owner_authorized=True) runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([implementation, final_open, final_merged]), agent, lambda _: None) result = runner._poll(state) @@ -3535,20 +3535,20 @@ def test_owner_authorized_merged_lifecycle_reconciles_and_cleans_up(self) -> Non ) result = EngineeringRunner( self.root, self.store, repository, github, - SequencedFakeAgent([AgentResult("WAITING", "codex/final", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]), lambda _: None, + SequencedFakeAgent([AgentResult("WAITING", "codex/finalize-autonomous-happy-path", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]), lambda _: None, )._poll(state) self.assertEqual(result.phase, "COMPLETE") self.assertTrue(result.terminal) self.assertEqual(result.implementation_merge_commit, "b" * 40) self.assertEqual(result.finalization_merge_commit, "c" * 40) - self.assertEqual(repository.cleanup_calls, [("codex/implementation", "codex/final")]) + self.assertEqual(repository.cleanup_calls, [("codex/implementation", "codex/finalize-autonomous-happy-path")]) self.assertEqual(github.merge_calls, []) def test_merged_finalization_returned_by_agent_is_reconciled_without_ready(self) -> None: implementation = PullRequestEvidence(21, "MERGED", True, True, "b" * 40) final_merged = PullRequestEvidence(22, "MERGED", True, True, "c" * 40) - agent = SequencedFakeAgent([AgentResult("WAITING", "codex/final", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]) + agent = SequencedFakeAgent([AgentResult("WAITING", "codex/finalize-already-finalized", 22), AgentResult("COMPLETE", terminal_condition="repository_reconciled", commit_sha="a" * 40)]) state = TransactionState( "already-finalized", "pcvantol/djconnect", str(self.prompt), "WAIT_FOR_TERMINAL_EVIDENCE", branch="codex/implementation", @@ -3571,6 +3571,27 @@ def test_finalization_checkpoint_prevents_duplicate_generation(self) -> None: self.assertEqual(result.pull_request, 23) self.assertEqual(runner.agent.prompts, []) + def test_finalization_result_on_an_uncheckpointed_branch_is_rejected(self) -> None: + state = TransactionState( + "finalization-branch-guard", "pcvantol/djconnect", str(self.prompt), + "FINALIZE_AGENT", owner_authorized=True, + transaction_kind="FINALIZATION", branch="codex/finalize-finalization-branch-guard", + finalization_branch="codex/finalize-finalization-branch-guard", + ) + runner = EngineeringRunner( + self.root, self.store, FakeRepository(), FakeGitHub([]), + FakeAgent(AgentResult("WAITING")), lambda _: None, + ) + + blocked = runner._advance_after_finalization_agent_result( + state, + AgentResult("COMPLETE", branch="qualification/incorrect-finalization", pull_request=22), + ) + + self.assertEqual(blocked.phase, "BLOCKED") + self.assertEqual(blocked.next_action, "finalization_branch_mismatch") + self.assertIsNone(blocked.finalization_pull_request) + def test_finalization_recovery_persists_existing_pr_without_invoking_agent(self) -> None: state = TransactionState( "recover-finalization", "pcvantol/djconnect", str(self.prompt), "FINALIZE_AGENT", diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 479a1ecb..8f6b95a1 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -140,7 +140,7 @@ def verify_receipt(data_root: Path, project: str, run_id: str) -> dict[str, obje return {"run_id": run_id, "assurance_reviews": observed, "terminal_phase": row[0]} -def verify_controlled_recovery(data_root: Path, checkout: Path, run_id: str, base: str) -> dict[str, object]: +def verify_controlled_recovery(data_root: Path, checkout: Path, run_id: str, base: str, project: str = "recovery") -> dict[str, object]: control = checkout / ".engineering" / "artifacts" / "provider-recovery-fault-injection" / f"{run_id}-EXECUTE_AGENT.json" consumed = json.loads(control.read_text(encoding="utf-8")) if consumed.get("kind") != "CONTROLLED_PROVIDER_INTERRUPTION" or consumed.get("phase") != "EXECUTE_AGENT": @@ -151,13 +151,65 @@ def verify_controlled_recovery(data_root: Path, checkout: Path, run_id: str, bas ).fetchone() if recovery != ("EXECUTE_AGENT", "RECOVERED", "SUCCESS"): raise RuntimeError(f"CONTROLLED_RECOVERY_LINEAGE_INVALID: {recovery}") - with urlopen(base + "/api/prompt-history?project=recovery", timeout=5) as response: # nosec B310 + with urlopen(base + f"/api/prompt-history?project={project}", timeout=5) as response: # nosec B310 history = json.loads(response.read()) if not isinstance(history, list) or not any(item.get("run_id") == run_id for item in history if isinstance(item, dict)): raise RuntimeError("CONTROLLED_RECOVERY_DASHBOARD_HISTORY_UNAVAILABLE") return {"run_id": run_id, "control": "CONSUMED", "recovery": "RECOVERED", "dashboard_history": "VISIBLE"} +def merge_github_pull_request(repository: str, pull_request: int) -> None: + """Cross the explicit human merge boundary of the disposable fixture.""" + subprocess.run( + ("gh", "pr", "merge", str(pull_request), "--repo", repository, "--merge", "--delete-branch"), + check=True, capture_output=True, text=True, + ) # nosec B603 + + +def run_id_for_submission(server: Path, data_root: Path, submission_id: str) -> str: + deadline = time.monotonic() + 120 + while time.monotonic() < deadline: + diagnosis = command(server, "submission-diagnose", "--data-root", str(data_root), "--submission-id", submission_id) + run_id = diagnosis.get("run_id") + if isinstance(run_id, str) and run_id: + return run_id + time.sleep(.2) + raise RuntimeError("MANAGED_GITHUB_RUN_ID_UNAVAILABLE") + + +def complete_github_managed_run( + server: Path, data_root: Path, submission_id: str, checkout: Path, + repository: str, implementation_branch: str, project: str, +) -> dict[str, object]: + """Verify the real remote implementation and Finalization handoffs to COMPLETE. + + The script is the designated human operator for this disposable fixture. + It never changes production merge authority: each merge is an explicit + GitHub CLI mutation on the exact repository named by the caller. + """ + implementation = wait_github_handoff(checkout, repository, implementation_branch) + pull_request = implementation["pull_request"] + if not isinstance(pull_request, dict) or not isinstance(pull_request.get("number"), int): + raise RuntimeError("MANAGED_GITHUB_IMPLEMENTATION_PR_INVALID") + merge_github_pull_request(repository, pull_request["number"]) + run_id = run_id_for_submission(server, data_root, submission_id) + finalization_branch = f"codex/finalize-{run_id}" + finalization = wait_github_handoff(checkout, repository, finalization_branch) + finalization_pr = finalization["pull_request"] + if not isinstance(finalization_pr, dict) or not isinstance(finalization_pr.get("number"), int): + raise RuntimeError("MANAGED_GITHUB_FINALIZATION_PR_INVALID") + merge_github_pull_request(repository, finalization_pr["number"]) + _, terminal_run_id = wait_terminal(server, data_root, submission_id) + if terminal_run_id != run_id: + raise RuntimeError("MANAGED_GITHUB_RUN_ID_CHANGED") + receipt = verify_receipt(data_root, project, run_id) + return { + **receipt, + "implementation": implementation, + "finalization": finalization, + } + + def submit(base: str, credential: str, project: str, repository: str, prompt: str, key: str) -> str: body = {"repository_id": repository, "producer": {"id": "installed-e2e", "type": "HUMAN", "version": "1"}, "prompt": prompt, "idempotency_key": key, "constraints": {"mode": "GENESIS" if "Genesis" in prompt else "MANAGED"}} request = Request(base + f"/v1/projects/{project}/submissions", data=json.dumps(body).encode(), method="POST", headers={"Content-Type": "application/json", "Authorization": f"Bearer {credential}"}) @@ -278,22 +330,45 @@ def main(argv: list[str] | None = None) -> int: credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", project, "--consumer-id", f"{mode}-e2e")["credential"]) submission = submit(base, credential, project, repository, prompt, f"{mode}-e2e") if mode == "managed" and github_write: - evidence[mode] = wait_github_handoff(managed, str(args.managed_github_repository), str(github_branch)) + evidence[mode] = complete_github_managed_run( + server, data, submission, managed, + str(args.managed_github_repository), str(github_branch), project, + ) continue _, run_id = wait_terminal(server, data, submission) evidence[mode] = verify_receipt(data, project, run_id) - recovery = root / "controlled-recovery" - create_repository(recovery, origin=root / "controlled-recovery-origin.git") - bind_project(server, data, project="recovery", repository="recovery-repo", checkout=recovery, push_declaration=True) + recovery_project, recovery_repository = "recovery", "recovery-repo" + if github_write: + # Reuse the installed, already bound dummy fixture after the + # first run reached COMPLETE. This proves interruption/retry + # with the same real GitHub provider adapter and remote write + # boundary, without inventing another authority declaration. + recovery, recovery_project, recovery_repository = managed, managed_project, managed_identity + recovery_branch = f"{github_branch}-recovery" + else: + recovery = root / "controlled-recovery" + create_repository(recovery, origin=root / "controlled-recovery-origin.git") + bind_project(server, data, project=recovery_project, repository=recovery_repository, checkout=recovery, push_declaration=True) control_ready.with_suffix(control_ready.suffix + ".enable").write_text("enabled\n", encoding="utf-8") - credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", "recovery", "--consumer-id", "recovery-e2e")["credential"]) - submission = submit(base, credential, "recovery", "recovery-repo", "Execution Mode: Managed\n\nInstalled controlled recovery qualification.", "controlled-recovery-e2e") + credential = str(command(server, "issue-consumer-credential", "--data-root", str(data), "--project-id", recovery_project, "--consumer-id", "recovery-e2e")["credential"]) + submission = submit(base, credential, recovery_project, recovery_repository, "Execution Mode: Managed\n\nInstalled controlled recovery qualification.", "controlled-recovery-e2e") run_id = arm_controlled_recovery(venv, data, recovery, control_ready) - _, terminal_run_id = wait_terminal(server, data, submission) - if terminal_run_id != run_id: - raise RuntimeError("CONTROLLED_RECOVERY_RUN_ID_CHANGED") - verify_receipt(data, "recovery", run_id) - evidence["controlled_recovery"] = verify_controlled_recovery(data, recovery, run_id, base) + if github_write: + recovery_evidence = complete_github_managed_run( + server, data, submission, recovery, str(args.managed_github_repository), + recovery_branch, recovery_project, + ) + if recovery_evidence["run_id"] != run_id: + raise RuntimeError("CONTROLLED_RECOVERY_RUN_ID_CHANGED") + else: + _, terminal_run_id = wait_terminal(server, data, submission) + if terminal_run_id != run_id: + raise RuntimeError("CONTROLLED_RECOVERY_RUN_ID_CHANGED") + recovery_evidence = verify_receipt(data, recovery_project, run_id) + evidence["controlled_recovery"] = { + **recovery_evidence, + **verify_controlled_recovery(data, recovery, run_id, base, recovery_project), + } finally: if not args.persistent_root: process.terminate(); process.wait(timeout=10) From 2c73649cd509645340a86eb9714f5592ca25a495 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:18:13 +0200 Subject: [PATCH 45/87] ci: advance canonical versions on branch pushes --- .github/workflows/canonical-versioning.yml | 63 +++++++++++++++++++ docs/development/LOCAL_AGENT_RUNNER.md | 11 ++++ .../test_platform_productization.py | 18 ++++++ tools/qualification/advance_platform_build.py | 15 +++-- 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/canonical-versioning.yml diff --git a/.github/workflows/canonical-versioning.yml b/.github/workflows/canonical-versioning.yml new file mode 100644 index 00000000..2c64f882 --- /dev/null +++ b/.github/workflows/canonical-versioning.yml @@ -0,0 +1,63 @@ +name: Canonical versioning + +on: + push: + branches: + - '**' + +# One writer per ref prevents two near-simultaneous pushes from creating two +# version commits on the same branch. +concurrency: + group: canonical-version-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write + +jobs: + feature-patch: + name: Add the first feature-branch patch version + if: github.ref_name != 'main' && !startsWith(github.ref_name, 'release-') && github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + with: + fetch-depth: 0 + - name: Detect an existing canonical version commit + id: existing + run: | + set -euo pipefail + git fetch --no-tags origin main + if git log --format=%s origin/main..HEAD | grep -Eq '^build: advance canonical EP patch version [0-9]+\.[0-9]+\.[0-9]+$'; then + echo 'present=true' >> "$GITHUB_OUTPUT" + else + echo 'present=false' >> "$GITHUB_OUTPUT" + fi + - name: Advance patch version once for this feature branch + if: steps.existing.outputs.present != 'true' + run: | + set -euo pipefail + version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump patch | sed -n 's/^EP_BUILD_VERSION=//p')" + python3 tools/qualification/platform_version_consistency.py --source-root . + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add pyproject.toml package.json package-lock.json src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json src/engineering_platform/templates/workspace-config.json src/engineering_platform/platform_version.py + git commit -m "build: advance canonical EP patch version ${version}" + git push origin "HEAD:${GITHUB_REF_NAME}" + + main-minor: + name: Advance the canonical minor version on main + if: github.ref_name == 'main' && github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + - name: Advance minor version and commit it to main + run: | + set -euo pipefail + version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump minor | sed -n 's/^EP_BUILD_VERSION=//p')" + python3 tools/qualification/platform_version_consistency.py --source-root . + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add pyproject.toml package.json package-lock.json src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json src/engineering_platform/templates/workspace-config.json src/engineering_platform/platform_version.py + git commit -m "build: advance canonical EP minor version ${version}" + git push origin HEAD:main diff --git a/docs/development/LOCAL_AGENT_RUNNER.md b/docs/development/LOCAL_AGENT_RUNNER.md index 48b94450..8b655b9d 100644 --- a/docs/development/LOCAL_AGENT_RUNNER.md +++ b/docs/development/LOCAL_AGENT_RUNNER.md @@ -40,6 +40,17 @@ is a separate Engineering Platform release. The private dashboard displays them with the corresponding live components, while its status bar displays the Engineering Platform version and Git commit. +`Canonical versioning` is the only CI writer of the checked-in release +projections. On the first non-bot push to a feature branch it adds one +`build: advance canonical EP patch version X.Y.Z` commit. On a non-bot push to +`main` it adds one `build: advance canonical EP minor version X.Y.0` commit. +The workflow serializes writes per ref, skips `release-*` branches, and uses +`advance_platform_build.py` so the package, manifest, configuration and +workspace-template projections remain identical. A protected branch must +explicitly allow the repository GitHub Actions token to create these bot +commits; otherwise the workflow correctly fails instead of silently claiming a +version bump. + At runner startup, `engineering-execution-host` reads the manifest and rejects an unsupported platform major version, older runner, older Bootstrap Contract, unsupported checkpoint/memory/report format or unsupported Codex CLI. Diagnostics state the diff --git a/tests/engineering/test_platform_productization.py b/tests/engineering/test_platform_productization.py index 241a4e3e..53fa1789 100644 --- a/tests/engineering/test_platform_productization.py +++ b/tests/engineering/test_platform_productization.py @@ -163,6 +163,24 @@ def test_canonical_wheel_build_advances_one_patch_across_all_projections(self) - if path.is_file(): self.assertIn("2.1.2", path.read_text(encoding="utf-8")) + def test_canonical_versioning_can_advance_a_minor_and_resets_patch(self) -> None: + import importlib.util + script = ROOT / "tools" / "qualification" / "advance_platform_build.py" + spec = importlib.util.spec_from_file_location("advance_platform_build", script) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for relative in _version_projection_files(): + target = root / relative + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text('version = "2.1.6"\n', encoding="utf-8") + self.assertEqual(module.advance(root, component="minor"), "2.2.0") + for path in root.rglob("*"): + if path.is_file(): + self.assertIn("2.2.0", path.read_text(encoding="utf-8")) + def test_release_build_can_set_an_exact_branch_version_across_all_projections(self) -> None: import importlib.util script = ROOT / "tools" / "qualification" / "advance_platform_build.py" diff --git a/tools/qualification/advance_platform_build.py b/tools/qualification/advance_platform_build.py index 5df5ce52..04e19661 100644 --- a/tools/qualification/advance_platform_build.py +++ b/tools/qualification/advance_platform_build.py @@ -52,19 +52,26 @@ def set_version(root: Path, version: str) -> str: return version -def advance(root: Path) -> str: - """Advance one patch number while retaining the current major/minor line.""" +def advance(root: Path, *, component: str = "patch") -> str: + """Advance one stable semantic-version component across all projections.""" current = _current_version(root) major, minor, patch = (int(part) for part in current.split(".")) - return set_version(root, f"{major}.{minor}.{patch + 1}") + if component == "patch": + target = f"{major}.{minor}.{patch + 1}" + elif component == "minor": + target = f"{major}.{minor + 1}.0" + else: + raise RuntimeError("version component must be patch or minor") + return set_version(root, target) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Advance one canonical EP wheel build number") parser.add_argument("--source-root", type=Path, default=Path.cwd()) parser.add_argument("--set-version", help="set all canonical projections to this exact stable X.Y.Z version") + parser.add_argument("--bump", choices=("patch", "minor"), default="patch", help="semantic-version component to advance when --set-version is absent") args = parser.parse_args(argv) - version = set_version(args.source_root, args.set_version) if args.set_version else advance(args.source_root) + version = set_version(args.source_root, args.set_version) if args.set_version else advance(args.source_root, component=args.bump) print(f"EP_BUILD_VERSION={version}") return 0 From f4dbf1f1170966e9255b42430dc24ed51d1ddcf3 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:18:52 +0200 Subject: [PATCH 46/87] fix: install versioned package before CI consistency check --- .github/workflows/canonical-versioning.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/canonical-versioning.yml b/.github/workflows/canonical-versioning.yml index 2c64f882..3a04daa3 100644 --- a/.github/workflows/canonical-versioning.yml +++ b/.github/workflows/canonical-versioning.yml @@ -38,6 +38,7 @@ jobs: run: | set -euo pipefail version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump patch | sed -n 's/^EP_BUILD_VERSION=//p')" + python3 -m pip install --quiet . python3 tools/qualification/platform_version_consistency.py --source-root . git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' @@ -55,6 +56,7 @@ jobs: run: | set -euo pipefail version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump minor | sed -n 's/^EP_BUILD_VERSION=//p')" + python3 -m pip install --quiet . python3 tools/qualification/platform_version_consistency.py --source-root . git config user.name 'github-actions[bot]' git config user.email '41898282+github-actions[bot]@users.noreply.github.com' From cb18ee0c2f6123e2e8303ee26dbaec52c290626b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 06:19:07 +0000 Subject: [PATCH 47/87] build: advance canonical EP patch version 2.1.7 --- package-lock.json | 4 ++-- package.json | 2 +- pyproject.toml | 2 +- src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json | 2 +- .../ENGINEERING_PLATFORM_VERSION.json | 8 ++++---- src/engineering_platform/platform_version.py | 2 +- src/engineering_platform/templates/workspace-config.json | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index afc5d911..334b28b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engineering-platform-browser-validation", - "version": "2.1.6", + "version": "2.1.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "engineering-platform-browser-validation", - "version": "2.1.6", + "version": "2.1.7", "devDependencies": { "@playwright/test": "1.62.1" } diff --git a/package.json b/package.json index 9fff394a..6627e156 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "engineering-platform-browser-validation", "private": true, - "version": "2.1.6", + "version": "2.1.7", "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 31ecd887..dfa0347a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "engineering-platform" -version = "2.1.6" +version = "2.1.7" 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 0cb659b7..94cd4cec 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.1.6", + "version": "2.1.7", "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 fd517cd1..fdffa64c 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.1.6", + "dashboard_version": "2.1.7", "handoff_protocol": 1, "memory_format": 2, "minimum_codex_cli": "0.146.0", "inbox_protocol": 1, - "platform_version": "2.1.6", + "platform_version": "2.1.7", "report_format": 2, - "runner_version": "2.1.6", + "runner_version": "2.1.7", "status_model": 1, "storage_schema": 41, - "watcher_version": "2.1.6" + "watcher_version": "2.1.7" } diff --git a/src/engineering_platform/platform_version.py b/src/engineering_platform/platform_version.py index 6268be2c..e39a4c08 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.1.6" +CURRENT_PLATFORM_VERSION = "2.1.7" MANIFEST_FIELDS = frozenset( { "platform_version", diff --git a/src/engineering_platform/templates/workspace-config.json b/src/engineering_platform/templates/workspace-config.json index 7ad3f49e..0b16b1c3 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.1.6", + "version": "2.1.7", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 From 62a445c933e0511c5fb2b647d8dcd33d09ad2cee Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:27:23 +0200 Subject: [PATCH 48/87] docs: reference canonical product versioning policy --- docs/development/LOCAL_AGENT_RUNNER.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/development/LOCAL_AGENT_RUNNER.md b/docs/development/LOCAL_AGENT_RUNNER.md index 8b655b9d..564e2ecc 100644 --- a/docs/development/LOCAL_AGENT_RUNNER.md +++ b/docs/development/LOCAL_AGENT_RUNNER.md @@ -51,6 +51,12 @@ explicitly allow the repository GitHub Actions token to create these bot commits; otherwise the workflow correctly fails instead of silently claiming a version bump. +The event policy is shared with Forge and Workspace and is canonically defined +by Forge Platform in [Canonical product versioning](https://github.com/pcvantol/forge-platform/blob/main/docs/architecture/CANONICAL_PRODUCT_VERSIONING.md). +Engineering Platform retains its richer package/manifest projection and its +own release-publishing authority; the shared policy grants neither publication +nor deployment authority. + At runner startup, `engineering-execution-host` reads the manifest and rejects an unsupported platform major version, older runner, older Bootstrap Contract, unsupported checkpoint/memory/report format or unsupported Codex CLI. Diagnostics state the From 8e6f2aa8a96cf006ef937a20ff9ff8f5493bb9c6 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:31:02 +0200 Subject: [PATCH 49/87] release: set canonical version 2.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- pyproject.toml | 2 +- src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json | 2 +- .../ENGINEERING_PLATFORM_VERSION.json | 8 ++++---- src/engineering_platform/platform_version.py | 2 +- src/engineering_platform/templates/workspace-config.json | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 334b28b9..cc1b387a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "engineering-platform-browser-validation", - "version": "2.1.7", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "engineering-platform-browser-validation", - "version": "2.1.7", + "version": "2.3.0", "devDependencies": { "@playwright/test": "1.62.1" } diff --git a/package.json b/package.json index 6627e156..cb78a80a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "engineering-platform-browser-validation", "private": true, - "version": "2.1.7", + "version": "2.3.0", "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 dfa0347a..9f6c513b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "engineering-platform" -version = "2.1.7" +version = "2.3.0" 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 94cd4cec..d0dc4a0c 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.1.7", + "version": "2.3.0", "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 fdffa64c..5b1ed1dc 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.1.7", + "dashboard_version": "2.3.0", "handoff_protocol": 1, "memory_format": 2, "minimum_codex_cli": "0.146.0", "inbox_protocol": 1, - "platform_version": "2.1.7", + "platform_version": "2.3.0", "report_format": 2, - "runner_version": "2.1.7", + "runner_version": "2.3.0", "status_model": 1, "storage_schema": 41, - "watcher_version": "2.1.7" + "watcher_version": "2.3.0" } diff --git a/src/engineering_platform/platform_version.py b/src/engineering_platform/platform_version.py index e39a4c08..603f92fd 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.1.7" +CURRENT_PLATFORM_VERSION = "2.3.0" MANIFEST_FIELDS = frozenset( { "platform_version", diff --git a/src/engineering_platform/templates/workspace-config.json b/src/engineering_platform/templates/workspace-config.json index 0b16b1c3..d6ebb6ff 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.1.7", + "version": "2.3.0", "generation": 2, "documentation_namespace": "engineering-platform", "capability_registry_version": 1 From 6db55aea59e7046fda4dafe7d4a0df4e578d6647 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:48:25 +0200 Subject: [PATCH 50/87] fix: provision deterministic runtime for installed E2E --- .../p_deterministic_execution_e2e.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 8f6b95a1..098bd9b8 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -25,6 +25,8 @@ CENTRAL_DATABASE_FILENAME = "epdata.sqlite" +SERVER_CONFIGURATION_FILENAME = "server.json" +QUALIFICATION_RUNTIME_VERSION = "0.153.4" def command(binary: Path, *args: str) -> dict[str, object]: @@ -87,6 +89,31 @@ def create_repository(path: Path, *, origin: Path | None = None) -> None: subprocess.run(("git", "--git-dir", str(origin), "symbolic-ref", "HEAD", "refs/heads/main"), check=True) # nosec B603 +def configure_deterministic_runtime(data_root: Path, root: Path) -> Path: + """Install the minimal managed-runtime contract inside this qualification only. + + Host preflight must remain real in the installed E2E. CI deliberately has + no account-wide EP-managed Codex installation, so the isolated data root + owns a tiny version-reporting launcher. The deterministic provider never + invokes it for agent work; it exists solely to exercise the same resolved + launcher and invocation checks that production performs. + """ + prefix = root / "managed-codex-cli" + executable = prefix / "bin" / "codex" + executable.parent.mkdir(parents=True) + executable.write_text(f"#!/bin/sh\nprintf 'codex {QUALIFICATION_RUNTIME_VERSION}\\n'\n", encoding="utf-8") + executable.chmod(0o755) + configuration_path = data_root / SERVER_CONFIGURATION_FILENAME + configuration = json.loads(configuration_path.read_text(encoding="utf-8")) + if not isinstance(configuration, dict) or set(configuration) != { + "version", "bind_host", "bind_port", "managed_codex_cli_prefix", + }: + raise RuntimeError("QUALIFICATION_SERVER_CONFIGURATION_INVALID") + configuration["managed_codex_cli_prefix"] = str(prefix.resolve()) + configuration_path.write_text(json.dumps(configuration, sort_keys=True) + "\n", encoding="utf-8") + return executable + + def wait_terminal(server: Path, data_root: Path, submission_id: str) -> tuple[str, str]: # Managed deliberately yields between the implementation merge and the # finalization/reconciliation polls; leave room for those bounded resumes. @@ -287,6 +314,7 @@ def main(argv: list[str] | None = None) -> int: server = venv / "bin" / "python" bind_port = args.bind_port or port() command(server, "init", "--data-root", str(data), "--bind-port", str(bind_port)) + configure_deterministic_runtime(data, root) genesis_host, genesis_target = root / "genesis-host", root / "genesis-target" create_repository(genesis_host) create_repository(genesis_target) From 81a4e7ec82bd366c926963a2968025ab1545bdb9 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 08:50:38 +0200 Subject: [PATCH 51/87] test: cover deterministic qualification runtime composition --- docs/engineering/EXECUTION_HOST_OPERATIONS.md | 17 ++++++ .../test_deterministic_execution_e2e.py | 61 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 tests/engineering/test_deterministic_execution_e2e.py diff --git a/docs/engineering/EXECUTION_HOST_OPERATIONS.md b/docs/engineering/EXECUTION_HOST_OPERATIONS.md index cb87b1ea..760e8c2a 100644 --- a/docs/engineering/EXECUTION_HOST_OPERATIONS.md +++ b/docs/engineering/EXECUTION_HOST_OPERATIONS.md @@ -159,6 +159,23 @@ same-run `RECOVERED` lineage, terminal assurance evidence, and the run in the dashboard's project-scoped history. The dedicated provider-recovery unit suite additionally qualifies unsafe and ambiguous recovery branches. +### Installed qualification runtime composition + +The installed E2E also exercises the real host-preflight runtime checks. Its +temporary CENTRAL root owns a minimal executable under +`managed-codex-cli/bin/codex`; it reports the fixed qualification runtime +version and is written into that root's `server.json` before the Server starts. +This is a composition fixture, not a provider fallback: the deterministic +qualification agent remains the only component that performs the test work. +The launcher exists so `runtime_executable` and `runtime_invocation` are +verified through the same configured EP-managed runtime boundary as a normal +installation. It is removed with the temporary qualification root and never +creates or relies on an account-wide Codex installation. + +Consequently, CI always runs Genesis, Managed and armed recovery with a real +preflight. The separate external GitHub profile below additionally proves the +remote-write adapter and merge boundaries; it is not invoked by ordinary CI. + ### Explicit external Managed GitHub qualification The default deterministic qualification never contacts GitHub. To prove the diff --git a/tests/engineering/test_deterministic_execution_e2e.py b/tests/engineering/test_deterministic_execution_e2e.py new file mode 100644 index 00000000..670e5725 --- /dev/null +++ b/tests/engineering/test_deterministic_execution_e2e.py @@ -0,0 +1,61 @@ +"""Regression tests for the installed deterministic execution qualification.""" + +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + + +def _qualification_module() -> object: + path = Path(__file__).parents[2] / "tools" / "qualification" / "p_deterministic_execution_e2e.py" + specification = importlib.util.spec_from_file_location("deterministic_execution_e2e", path) + if specification is None or specification.loader is None: + raise RuntimeError("qualification module is unavailable") + module = importlib.util.module_from_spec(specification) + specification.loader.exec_module(module) + return module + + +class DeterministicExecutionE2ETests(unittest.TestCase): + def setUp(self) -> None: + self.module = _qualification_module() + self.temporary = tempfile.TemporaryDirectory() + self.root = Path(self.temporary.name) + self.data = self.root / "central" + self.data.mkdir() + self.configuration = self.data / "server.json" + + def tearDown(self) -> None: + self.temporary.cleanup() + + def test_isolated_runtime_is_executable_and_becomes_server_runtime_authority(self) -> None: + self.configuration.write_text(json.dumps({ + "version": 2, + "bind_host": "127.0.0.1", + "bind_port": 8765, + "managed_codex_cli_prefix": "/unavailable/production-runtime", + }), encoding="utf-8") + + executable = self.module.configure_deterministic_runtime(self.data, self.root) + + self.assertEqual( + subprocess.run((str(executable), "--version"), check=True, text=True, capture_output=True).stdout.strip(), + f"codex {self.module.QUALIFICATION_RUNTIME_VERSION}", + ) + configuration = json.loads(self.configuration.read_text(encoding="utf-8")) + self.assertEqual(configuration["managed_codex_cli_prefix"], str(executable.parent.parent.resolve())) + self.assertEqual(executable, self.root / "managed-codex-cli" / "bin" / "codex") + + def test_isolated_runtime_rejects_an_unexpected_server_configuration_shape(self) -> None: + self.configuration.write_text("{}", encoding="utf-8") + + with self.assertRaisesRegex(RuntimeError, "QUALIFICATION_SERVER_CONFIGURATION_INVALID"): + self.module.configure_deterministic_runtime(self.data, self.root) + + +if __name__ == "__main__": + unittest.main() From b385d29886d625cf6d6cabaaa268abb0d68312a8 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 09:12:57 +0200 Subject: [PATCH 52/87] test: restore qualification coverage contract --- .../test_provider_recovery_controller.py | 16 +++ .../engineering/test_qualification_runtime.py | 125 ++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 tests/engineering/test_qualification_runtime.py diff --git a/tests/engineering/test_provider_recovery_controller.py b/tests/engineering/test_provider_recovery_controller.py index 6a96a3dd..07bdc5ad 100644 --- a/tests/engineering/test_provider_recovery_controller.py +++ b/tests/engineering/test_provider_recovery_controller.py @@ -3,10 +3,12 @@ from pathlib import Path from tempfile import TemporaryDirectory import os +import sqlite3 import unittest from engineering_platform.agent_state import StateStore, TransactionState from engineering_platform.provider_recovery import ( + _connection, ControlledInterruptionControlError, arm_controlled_interruption, controlled_interruption_status, @@ -22,6 +24,7 @@ consume_controlled_interruption_hook, watcher_resume_action, ) +from engineering_platform.storage import EngineeringStorageError class ProviderRecoveryControllerTests(unittest.TestCase): @@ -41,6 +44,19 @@ def setUp(self) -> None: def tearDown(self) -> None: self.temp.cleanup() + def test_explicit_central_binding_requires_an_existing_database(self) -> None: + missing = self.root / "missing-central.sqlite" + with self.assertRaisesRegex(EngineeringStorageError, "CENTRAL recovery database"): + _connection(self.root, missing) + + central = self.root / "central.sqlite" + sqlite3.connect(central).close() + connection = _connection(self.root, central) + try: + self.assertEqual(connection.execute("PRAGMA foreign_keys").fetchone(), (1,)) + finally: + connection.close() + def test_claim_start_terminal_lineage_is_exactly_once(self) -> None: self.assertTrue(transition_recovery_state( self.root, run_id=self.run_id, expected="RECOVERY_AVAILABLE", target="RECOVERY_STARTING", diff --git a/tests/engineering/test_qualification_runtime.py b/tests/engineering/test_qualification_runtime.py new file mode 100644 index 00000000..b5c1a1cb --- /dev/null +++ b/tests/engineering/test_qualification_runtime.py @@ -0,0 +1,125 @@ +"""Unit coverage for the explicit, local-only qualification provider seam.""" + +from __future__ import annotations + +import os +from pathlib import Path +import subprocess +import tempfile +import unittest +from types import SimpleNamespace +from unittest.mock import patch + +from engineering_platform.qualification_runtime import ( + DeterministicQualificationAgent, + LocalQualificationGitHub, +) + + +class DeterministicQualificationRuntimeTests(unittest.TestCase): + def setUp(self) -> None: + self.temporary = tempfile.TemporaryDirectory() + self.root = Path(self.temporary.name) + subprocess.run(("git", "init", "-b", "main", str(self.root)), check=True, capture_output=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("fixture\n", encoding="utf-8") + subprocess.run(("git", "-C", str(self.root), "add", "README.md"), check=True) + subprocess.run(("git", "-C", str(self.root), "commit", "-m", "fixture"), check=True, capture_output=True) + + def tearDown(self) -> None: + self.temporary.cleanup() + + def test_local_genesis_managed_and_reconciliation_results_are_deterministic(self) -> None: + agent = DeterministicQualificationAgent() + process = [] + agent.set_process_callback(process.append) + + managed = agent.invoke(self.root, "implement the approved work") + reconciled = agent.invoke(self.root, "the sole automatic post-finalization reconciliation") + genesis = agent.invoke(self.root, f"Execution mode: Genesis\nTarget repository: {self.root}") + + self.assertEqual(process[0]["pid"], os.getpid()) + self.assertEqual(managed.branch, "qualification-managed") + self.assertEqual(managed.pull_request, 1) + self.assertEqual(reconciled.terminal_condition, "repository_reconciled") + self.assertEqual(genesis.terminal_condition, "local_commit_reconciled") + self.assertEqual(genesis.repository_path, str(self.root.resolve())) + self.assertTrue(agent.available()) + self.assertEqual(agent.version(), "0.153.4") + + def test_finalization_branch_contract_and_external_target_gate(self) -> None: + agent = DeterministicQualificationAgent() + prompt = "Create Finalization PR on exactly `qualification-finalize`." + self.assertEqual(agent._finalization_branch(prompt), "qualification-finalize") + with self.assertRaisesRegex(RuntimeError, "QUALIFICATION_FINALIZATION_BRANCH_UNAVAILABLE"): + agent._finalization_branch("no branch") + with patch.dict(os.environ, {}, clear=True): + self.assertFalse(agent._github_write_target(self.root)) + with patch.dict(os.environ, { + "EP_QUALIFICATION_GITHUB_WRITE_FLOW": "1", + "EP_QUALIFICATION_GITHUB_REPOSITORY": "owner/repository", + }, clear=True), patch("engineering_platform.qualification_runtime.subprocess.run") as run: + run.return_value.returncode = 0 + run.return_value.stdout = "https://github.com/owner/repository.git\n" + self.assertTrue(agent._github_write_target(self.root)) + run.return_value.stdout = "https://github.com/other/repository.git\n" + self.assertFalse(agent._github_write_target(self.root)) + run.return_value.returncode = 1 + self.assertFalse(agent._github_write_target(self.root)) + + def test_controlled_interruption_window_is_explicit_and_bounded(self) -> None: + agent = DeterministicQualificationAgent() + ready = self.root / "arm-ready.json" + enable = ready.with_suffix(".json.enable") + enable.write_text("enabled\n", encoding="utf-8") + ready.with_suffix(".json.continue").write_text("continue\n", encoding="utf-8") + with patch.dict(os.environ, {"EP_QUALIFICATION_CONTROL_ARM_READY_FILE": str(ready)}, clear=True): + self.assertIsNone(agent.wait_for_controlled_interruption_arm(self.root, SimpleNamespace(run_id="run-a"))) + self.assertEqual(ready.read_text(encoding="utf-8"), '{"run_id": "run-a", "phase": "EXECUTE_AGENT"}') + with patch.dict(os.environ, {}, clear=True): + self.assertIsNone(agent.wait_for_controlled_interruption_arm(self.root, SimpleNamespace(run_id="run-a"))) + + def test_external_handoff_writes_only_the_explicit_fixture_contract(self) -> None: + agent = DeterministicQualificationAgent() + (self.root / ".engineering-platform").mkdir() + completed = SimpleNamespace(stdout="17\n", returncode=0) + environment = { + "EP_QUALIFICATION_GITHUB_REPOSITORY": "owner/fixture", + "EP_QUALIFICATION_GITHUB_BRANCH": "qualification-managed", + } + with patch.dict(os.environ, environment, clear=True), patch( + "engineering_platform.qualification_runtime.subprocess.run", return_value=completed + ) as run: + handoff = agent._create_github_managed_handoff(self.root) + recovery = agent._create_github_managed_handoff(self.root, prompt="controlled recovery qualification") + finalization = agent._create_github_managed_handoff( + self.root, finalization=True, prompt="Finalization PR on exactly `qualification-finalize`." + ) + self.assertEqual((handoff.branch, handoff.pull_request), ("qualification-managed", 17)) + self.assertEqual(recovery.branch, "qualification-managed-recovery") + self.assertEqual(finalization.branch, "qualification-finalize") + self.assertTrue((self.root / ".engineering-platform" / "managed-github-e2e-proof.json").is_file()) + self.assertTrue((self.root / ".engineering-platform" / "managed-github-e2e-finalization-proof.json").is_file()) + self.assertGreaterEqual(run.call_count, 21) + with patch.dict(os.environ, {}, clear=True): + with self.assertRaisesRegex(RuntimeError, "QUALIFICATION_GITHUB_WRITE_CONFIGURATION_INVALID"): + agent._create_github_managed_handoff(self.root) + self.assertEqual(agent.review(self.root, SimpleNamespace(reviewer="quality"), "objective").reviewer, "quality") + + def test_local_github_adapter_models_open_then_merged_evidence(self) -> None: + adapter = LocalQualificationGitHub(self.root) + opened = adapter.pull_request(7) + merged = adapter.pull_request(7) + + self.assertEqual((opened.state, opened.head_branch), ("OPEN", "qualification-managed")) + self.assertEqual((merged.state, merged.head_branch), ("MERGED", "qualification-managed")) + self.assertTrue(merged.merge_commit) + self.assertIsNone(adapter.pull_request_for_head_branch("ignored")) + self.assertFalse(adapter.normalize_markdown_body(7)) + self.assertIsNone(adapter.ready(7)) + self.assertIsNone(adapter.merge(7)) + + +if __name__ == "__main__": + unittest.main() From d67d0160d8f34cb7bd24c4d5ffef0c451d807dc3 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 09:45:52 +0200 Subject: [PATCH 53/87] fix: enforce current run assurance lifecycle --- src/engineering_platform/agent_state.py | 42 ++- src/engineering_platform/capability_review.py | 36 ++- .../execution_executor.py | 48 ++- src/engineering_platform/execution_host.py | 298 ++++++++++++++---- .../qualification_runtime.py | 62 +++- .../submission_service.py | 81 ++++- tests/engineering/test_execution_host.py | 46 +-- .../engineering/test_qualification_runtime.py | 4 +- tests/engineering/test_submission_service.py | 29 ++ .../p_deterministic_execution_e2e.py | 4 +- 10 files changed, 526 insertions(+), 124 deletions(-) diff --git a/src/engineering_platform/agent_state.py b/src/engineering_platform/agent_state.py index bab391cc..f0c46e72 100644 --- a/src/engineering_platform/agent_state.py +++ b/src/engineering_platform/agent_state.py @@ -140,6 +140,7 @@ class TransactionState: # earlier finding into a pass. assurance_profile: dict[str, str] | None = None assurance_reviews: tuple[dict[str, object], ...] = () + assurance_resolutions: tuple[dict[str, str], ...] = () repair_iterations: int = 0 repair_audit: tuple[dict[str, str], ...] = () local_validation_iterations: int = 0 @@ -179,6 +180,7 @@ def from_dict(cls, raw: object) -> "TransactionState": "quality_evidence": (), "assurance_profile": None, "assurance_reviews": (), + "assurance_resolutions": (), "repair_iterations": 0, "repair_audit": (), "local_validation_iterations": 0, @@ -203,6 +205,8 @@ def from_dict(cls, raw: object) -> "TransactionState": raw = {**raw, "quality_evidence": tuple(raw["quality_evidence"])} if isinstance(raw.get("assurance_reviews"), list): raw = {**raw, "assurance_reviews": tuple(raw["assurance_reviews"])} + if isinstance(raw.get("assurance_resolutions"), list): + raw = {**raw, "assurance_resolutions": tuple(raw["assurance_resolutions"])} if isinstance(raw.get("repair_audit"), list): raw = {**raw, "repair_audit": tuple(raw["repair_audit"])} if isinstance(raw.get("local_validation_audit"), list): @@ -283,17 +287,24 @@ def from_dict(cls, raw: object) -> "TransactionState": ) ): raise StateError("checkpoint local validation audit is invalid or unsafe") + repair_audit_fields = audit_fields | {"repair_id", "origin", "input_candidate_sha", "dispatch_id"} if ( not isinstance(state.repair_audit, tuple) or len(state.repair_audit) > 3 or any( - not isinstance(item, dict) or set(item) != audit_fields + not isinstance(item, dict) or set(item) not in (audit_fields, repair_audit_fields) or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values()) or not item["iteration"].isdigit() or int(item["iteration"]) < 1 or item["outcome"] not in {"planned", "submitted_for_recheck", "agent_failed", "agent_timed_out"} or (item["commit_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["commit_sha"])) + or (set(item) == repair_audit_fields and ( + not re.fullmatch(r"repair:[A-Za-z0-9_.:-]+:[1-3]", item["repair_id"]) + or item["origin"] not in {"validation", "quality", "security", "hosted", "finalization"} + or (item["input_candidate_sha"] != "not_recorded" and not re.fullmatch(r"[0-9a-f]{40}", item["input_candidate_sha"])) + )) for item in state.repair_audit ) + or len({item.get("repair_id", f"legacy:{item['iteration']}") for item in state.repair_audit}) != len(state.repair_audit) ): raise StateError("checkpoint repair audit is invalid or unsafe") if ( @@ -367,21 +378,24 @@ 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"} if state.assurance_profile is not None and ( not isinstance(state.assurance_profile, dict) - or set(state.assurance_profile) != profile_fields + or set(state.assurance_profile) not in (profile_fields, current_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"]) ): raise StateError("checkpoint assurance profile is invalid") review_fields = {"reviewer", "status", "candidate_sha", "profile_digest", "invocation_id", "findings"} + current_review_fields = review_fields | {"contract_version", "started_at", "completed_at"} finding_fields = {"id", "fingerprint", "category", "criterion", "observation", "severity", "confidence", "blocking", "disposition"} + current_finding_fields = finding_fields | {"evidence_ref"} if ( not isinstance(state.assurance_reviews, tuple) or len(state.assurance_reviews) > 16 or any( - not isinstance(review, dict) or set(review) != review_fields + not isinstance(review, dict) or set(review) not in (review_fields, current_review_fields) or review.get("reviewer") not in {"quality", "security"} or review.get("status") not in {"PASS", "FAIL", "UNRESOLVED"} or not isinstance(review.get("candidate_sha"), str) or not re.fullmatch(r"[0-9a-f]{40}", review["candidate_sha"]) @@ -389,18 +403,38 @@ def from_dict(cls, raw: object) -> "TransactionState": or not isinstance(review.get("invocation_id"), str) or not review["invocation_id"] or not isinstance(review.get("findings"), list) or len(review["findings"]) > 12 or any( - not isinstance(finding, dict) or set(finding) != finding_fields + not isinstance(finding, dict) or set(finding) not in (finding_fields, current_finding_fields) or not all(isinstance(value, str) and value and len(value) <= 240 and value == redact_diagnostic(value, limit=240) for key, value in finding.items() if key != "blocking") or finding.get("severity") not in {"LOW", "MEDIUM", "HIGH", "CRITICAL"} or finding.get("confidence") not in {"LOW", "MEDIUM", "HIGH"} or finding.get("disposition") not in {"OPEN", "RESOLVED", "REJECTED", "NON_BLOCKING"} or not isinstance(finding.get("blocking"), bool) + or ("evidence_ref" in finding and (not isinstance(finding["evidence_ref"], str) or not finding["evidence_ref"])) for finding in review["findings"] ) + or (set(review) == current_review_fields and ( + review.get("contract_version") != "1.0" + or not isinstance(review.get("started_at"), str) + or not isinstance(review.get("completed_at"), str) + )) for review in state.assurance_reviews ) ): raise StateError("checkpoint assurance review evidence is invalid") + resolution_fields = {"finding_id", "disposition", "resolution_ref", "candidate_sha"} + if ( + not isinstance(state.assurance_resolutions, tuple) + or len(state.assurance_resolutions) > 128 + or any( + not isinstance(item, dict) or set(item) != resolution_fields + or not all(isinstance(value, str) and value and len(value) <= MAX_DIAGNOSTIC_LENGTH and value == redact_diagnostic(value) for value in item.values()) + or item["disposition"] not in {"RESOLVED", "REJECTED", "RISK_ACCEPTED"} + or not re.fullmatch(r"[0-9a-f]{40}", item["candidate_sha"]) + for item in state.assurance_resolutions + ) + or len({item["finding_id"] for item in state.assurance_resolutions}) != len(state.assurance_resolutions) + ): + raise StateError("checkpoint assurance resolutions are invalid or unsafe") if not isinstance(state.terminal, bool) or state.terminal != (state.phase in {"COMPLETE", "BLOCKED", "FAILED"}): raise StateError("checkpoint terminal flag conflicts with phase") return state diff --git a/src/engineering_platform/capability_review.py b/src/engineering_platform/capability_review.py index a9e3e331..f09c2049 100644 --- a/src/engineering_platform/capability_review.py +++ b/src/engineering_platform/capability_review.py @@ -44,6 +44,10 @@ "documentation": "Documentation Reviewer", "finalization": "Finalization Reviewer", } +MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION = "1.0" +MANDATORY_FINDING_FIELDS = frozenset({ + "id", "observation", "category", "criterion", "severity", "confidence", "evidence_ref", +}) PRODUCT_MATCHERS = { "apple_platform": (("apps/apple/", "engineering-platform-app", "swiftui", "watchos", "macos", "ios"), "Apple platform capability"), "windows_platform": (("apps/windows/", "engineering-platform-windows", "maui", "windows packaging"), "Windows platform capability"), @@ -69,6 +73,11 @@ class ReviewerResult: reviewer: str contribution: str recommendations: tuple[str, ...] = () + findings: tuple[dict[str, str], ...] = () + # In-process deterministic/test adapters construct this typed result only + # after choosing the mandatory contract. Raw provider JSON is still + # required to carry this field by ``CodexCliClient.review``. + contract_version: str | None = MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION failed: bool = False usage: dict[str, object] = field(default_factory=dict) runtime_metadata: dict[str, object] = field(default_factory=dict) @@ -146,12 +155,15 @@ def invoke(selection: ReviewerSelection) -> ReviewerResult: result = ReviewerResult( selection.reviewer, redact_diagnostic(result.contribution, limit=240), - tuple(redact_diagnostic(value, limit=240) for value in result.recommendations[:3]), + tuple(redact_diagnostic(value, limit=240) for value in result.recommendations), + tuple(dict(item) for item in result.findings if isinstance(item, dict)), + result.contract_version, result.failed, result.usage, result.runtime_metadata, result.churn, result.duration_seconds, + result.usage_snapshots, ) except Exception: # Reviewer failure is advisory and cannot block the transaction. result = ReviewerResult(selection.reviewer, "Reviewer failed; primary review continues.", failed=True) @@ -176,6 +188,28 @@ def reconciled_recommendations(results: tuple[ReviewerResult, ...]) -> tuple[str return tuple(accepted[:8]) +def mandatory_findings(result: ReviewerResult) -> tuple[dict[str, str], ...] | None: + """Return validated mandatory output, never advice-shaped pseudo-evidence.""" + if result.failed or result.contract_version != MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION: + return None + if not isinstance(result.findings, tuple) or len(result.findings) > 64: + return None + normalized: list[dict[str, str]] = [] + seen: set[str] = set() + for finding in result.findings: + if not isinstance(finding, dict) or set(finding) != MANDATORY_FINDING_FIELDS: + return None + if any(not isinstance(value, str) or not value.strip() or len(value) > 240 for value in finding.values()): + return None + if finding["severity"] not in {"LOW", "MEDIUM", "HIGH", "CRITICAL"} or finding["confidence"] not in {"LOW", "MEDIUM", "HIGH"}: + return None + if finding["id"] in seen: + return None + seen.add(finding["id"]) + normalized.append({key: redact_diagnostic(value, limit=240) for key, value in finding.items()}) + return tuple(normalized) + + def records_for_storage(selections: tuple[ReviewerSelection, ...], results: tuple[ReviewerResult, ...]) -> tuple[dict[str, object], ...]: by_reviewer = {result.reviewer: result for result in results} records: list[dict[str, object]] = [] diff --git a/src/engineering_platform/execution_executor.py b/src/engineering_platform/execution_executor.py index 47fc14c0..8b1732fb 100644 --- a/src/engineering_platform/execution_executor.py +++ b/src/engineering_platform/execution_executor.py @@ -16,7 +16,12 @@ from threading import Event, Thread from typing import Callable, Mapping -from .capability_review import ReviewerResult, ReviewerSelection, reviewer_prompt +from .capability_review import ( + MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, + ReviewerResult, + ReviewerSelection, + reviewer_prompt, +) from .codex_observability import codex_final_message as _codex_final_message, extract_codex_runtime_metadata, extract_codex_usage from .evidence_projection import ToolProxyEnvironment from .execution_context import additional_workspace_write_roots @@ -382,14 +387,31 @@ def review( schema = { "type": "object", "additionalProperties": False, - "required": ["contribution", "recommendations"], + "required": ["contract_version", "contribution", "recommendations", "findings"], "properties": { + "contract_version": {"type": "string", "const": MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION}, "contribution": {"type": "string", "maxLength": 240}, "recommendations": { "type": "array", - "maxItems": 3, + "maxItems": 64, "items": {"type": "string", "maxLength": 240}, }, + "findings": { + "type": "array", "maxItems": 64, + "items": { + "type": "object", "additionalProperties": False, + "required": ["id", "observation", "category", "criterion", "severity", "confidence", "evidence_ref"], + "properties": { + "id": {"type": "string", "maxLength": 240}, + "observation": {"type": "string", "maxLength": 240}, + "category": {"type": "string", "maxLength": 240}, + "criterion": {"type": "string", "maxLength": 240}, + "severity": {"type": "string", "enum": ["LOW", "MEDIUM", "HIGH", "CRITICAL"]}, + "confidence": {"type": "string", "enum": ["LOW", "MEDIUM", "HIGH"]}, + "evidence_ref": {"type": "string", "maxLength": 240}, + }, + }, + }, }, } state_directory = root / ".engineering" @@ -444,6 +466,8 @@ def review( selection.reviewer, str(raw["contribution"]), tuple(str(value) for value in raw["recommendations"]), + tuple(dict(value) for value in raw["findings"]), + str(raw["contract_version"]), usage=dict(self.last_usage), runtime_metadata=dict(self.last_runtime_metadata), churn=dict(self.last_churn), duration_seconds=self.last_execution_seconds, usage_snapshots=self.last_usage_snapshots, @@ -528,7 +552,7 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: "codex", "exec", "--sandbox", - MANAGED_EXECUTION_SANDBOX, + getattr(self, "_sandbox_override", MANAGED_EXECUTION_SANDBOX), "-C", str(root), "--json", @@ -605,6 +629,22 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: interruption_reason=interruption, ) from error + def validate(self, root: Path, prompt: str) -> AgentResult: + """Run the local-validation provider turn without repository writes. + + The host, rather than an instruction in the prompt, selects the + sandbox. The override is scoped to this synchronous call so a later + implementation, repair, or publication invocation retains its normal + bounded write capability. + """ + if hasattr(self, "_sandbox_override"): + raise RunnerError("nested validation sandbox override is invalid") + self._sandbox_override = "read-only" + try: + return self.invoke(root, prompt) + finally: + del self._sandbox_override + def _run_invocation( self, command: tuple[str, ...], root: Path, environment: Mapping[str, str] | None = None ) -> subprocess.CompletedProcess[str]: diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index e941d95e..3e82b903 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -23,8 +23,10 @@ from .agent_state import MAX_COMMIT_EVIDENCE_RECORDS, StateError, StateStore, TransactionState, redact_diagnostic, verified_commit_evidence_record from .capability_review import ( + MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, ReviewerResult, ReviewerSelection, + mandatory_findings, records_for_storage, run_reviews, select_reviewers, @@ -1000,7 +1002,12 @@ def _record_repair_audit(self, state: TransactionState, *, failed_checks: str, o outcome=outcome, empty_summary="Agent invocation did not return a repair summary.", ) - if state.repair_audit and state.repair_audit[-1].get("iteration") == str(state.repair_iterations) and state.repair_audit[-1].get("outcome") == "planned": + previous = state.repair_audit[-1] if state.repair_audit else None + if previous and previous.get("iteration") == str(state.repair_iterations) and previous.get("outcome") == "planned": + # The reservation identity is immutable. A retry/recovery updates + # the recorded outcome but cannot consume a second repair round. + if "repair_id" in previous: + record.update({key: previous[key] for key in ("repair_id", "origin", "input_candidate_sha", "dispatch_id")}) return replace(state, repair_audit=state.repair_audit[:-1] + (record,)) return replace(state, repair_audit=state.repair_audit + (record,)) @@ -1011,10 +1018,12 @@ def _repair_plan(self, state: TransactionState) -> dict[str, str] | None: plan = state.repair_audit[-1] if plan.get("iteration") != str(state.repair_iterations) or plan.get("outcome") != "planned": return None + if "repair_id" in plan and plan["repair_id"] != f"repair:{state.run_id}:{state.repair_iterations}": + return None return plan def _advance_after_repair_agent_result(self, repair: TransactionState, result: AgentResult) -> TransactionState: - """Apply live or recovered Repair success using its persisted plan.""" + """Revalidate and re-review every repaired candidate before delivery.""" plan = self._repair_plan(repair) if plan is None: return self._save_terminal(repair, "BLOCKED", "repair_plan_missing", "Repair result cannot be resumed without its persisted repair plan.") @@ -1026,9 +1035,38 @@ def _advance_after_repair_agent_result(self, repair: TransactionState, result: A self.store.save(repair) if result.terminal_state in {"BLOCKED", "FAILED"}: return self._save_terminal(repair, result.terminal_state, "external_action_required", result.diagnostic) - if result.pull_request != repair.pull_request: + 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.") - return self._poll(replace(repair, phase="WAIT_FOR_TERMINAL_EVIDENCE", next_action="poll_required_checks"), result) + repaired_result = replace( + result, + branch=result.branch or repair.branch, + pull_request=repair.pull_request, + ) + if repair.execution_mode == "GENESIS": + target = Path(repaired_result.repository_path).expanduser() if repaired_result.repository_path else None + if target is None: + return self._save_terminal(repair, "BLOCKED", "repair_candidate_unavailable", "Genesis repair did not return its local candidate repository." ) + reviewed, repaired_result = self._run_quality_assurance(repair, repaired_result, assurance_root=target) + if reviewed.terminal or reviewed.phase == "REPAIR_AGENT": + return reviewed + return self._reconcile_genesis_result(reviewed, repaired_result) + validated, validation_result = self._run_local_repository_validation(repair, repaired_result) + if validated.terminal: + return validated + if validation_result.terminal_state != "COMPLETE" or self._has_failed_validation_evidence(validation_result): + if validated.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: + return self._save_terminal(validated, "BLOCKED", "repair_budget_exhausted", "Repaired candidate did not pass local validation before the run-wide repair budget was exhausted.") + return self._repair(validated, "local validation failed. Repair the recorded validation findings for the current candidate.") + reviewed, reviewed_result = self._run_quality_assurance( + validated, replace(validation_result, pull_request=repair.pull_request, branch=repair.branch) + ) + if reviewed.terminal or reviewed.phase == "REPAIR_AGENT": + return reviewed + try: + evidence = self.repository.inspect(self.root) + except RunnerError: + return self._save_terminal(reviewed, "BLOCKED", "repair_candidate_unavailable", "Repaired Managed candidate could not be inspected after assurance.") + return self._continue_after_quality_control(reviewed, reviewed_result, evidence) def _record_local_validation_audit(self, state: TransactionState, *, result: AgentResult | None, outcome: str, profile: ValidationProfile) -> TransactionState: """Append one bounded local-validation iteration without sharing PR repair budget.""" @@ -1314,7 +1352,8 @@ def command_boundary(event: str, command_id: str, command: str, exit_code: int | terminal_condition="provider_turn_interrupted", interruption_reason="controlled_qualification_interruption", ) - result = self.agent.invoke(self.root, prompt) + invocation = getattr(self.agent, "validate", self.agent.invoke) if local_validation else self.agent.invoke + result = invocation(self.root, prompt) except KeyboardInterrupt as error: # A managed SIGINT/SIGTERM while the provider is active means no # valid AgentResult exists. Persist the canonical interruption @@ -1507,6 +1546,11 @@ 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" ): replacement_id = recovery.get("replacement_invocation_id") if ( @@ -1609,7 +1653,7 @@ def _invoke_agent_with_timing(self, state: TransactionState, prompt: str, *, rep def _run_local_repository_validation( self, state: TransactionState, implementation: AgentResult ) -> tuple[TransactionState, AgentResult]: - """Run the bounded, mutable local gate before an implementation PR exists.""" + """Run the read-only local gate before first implementation publication.""" if state.action_intent == "VALIDATION_ONLY": # This gate is a delivery-only boundary. A producer-authorized # qualification run may supply validation evidence but must never @@ -1620,7 +1664,15 @@ def _run_local_repository_validation( # Never rewrite that evidence; new managed prompts are instructed to # stop before PR creation and therefore enter this gate normally. if implementation.pull_request: - return state, implementation + # A PR that predates this assurance contract is preserved as + # immutable lineage evidence. A newly-started run cannot use a + # provider-returned PR to skip the publication gate. + if state.implementation_pull_request == implementation.pull_request: + return state, implementation + return self._save_terminal( + state, "BLOCKED", "implementation_pr_before_assurance", + "A new Managed run returned an implementation pull request before local validation and mandatory assurance.", + ), implementation if not branch: return self._save_terminal( state, "BLOCKED", "local_validation_scope", "Implementation must return one branch and no pull request before local validation." @@ -1630,21 +1682,10 @@ def _run_local_repository_validation( next_action="run_local_repository_validation", local_validation_iterations=0, local_validation_audit=(), ) - for iteration in range(1, MAX_LOCAL_REPOSITORY_VALIDATION_ATTEMPTS + 1): - # The initial local check is not a repair. Every subsequent - # corrective validation dispatch consumes the same durable budget - # used by assurance, hosted-check and finalization repairs. - if iteration > 1: - if validation.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: - return self._save_terminal( - validation, "BLOCKED", "repair_budget_exhausted", - "Local validation still requires correction after the run-wide repair budget was exhausted.", - ), implementation - validation = replace(validation, repair_iterations=validation.repair_iterations + 1) - validation = self._record_repair_audit( - validation, failed_checks="validation: required local control", objective="Repair bounded local validation findings.", - result=None, outcome="planned", - ) + # The first validation is a measurement, never a corrective provider + # turn. A failed measurement is routed through ``_repair`` by the + # caller, where it consumes the single run-wide repair budget. + for iteration in (1,): try: profile = classify(changed_paths(self.root, "main")) except OSError: @@ -1670,12 +1711,12 @@ def _run_local_repository_validation( write_live_status(self.root, validation, validation.next_action) instruction = f""" -Local repository validation gate — iteration {iteration} of {MAX_LOCAL_REPOSITORY_VALIDATION_ATTEMPTS}: +Local repository validation gate — read-only measurement: - Stay on exactly `{branch}`. Do not merge or change scope. - Diff-derived validation profile: `{profile.tier}`. Required evidence: {"; ".join(profile.commands)}. If the diff is unavailable or scope becomes mixed, use the full required suite. -- You may correct only the bounded production code and its tests, commit and push those corrections, then rerun the required validation. -- If validation still fails, return `WAITING` with a concise safe diagnostic; the host may allow the next bounded iteration. -- Create one draft implementation pull request only after the required local validation passes. Return that same branch and PR number. Never poll remote checks. +- Do not modify files, index, branch, commits, remotes, pull requests, or external state. The host enforces a read-only provider sandbox. +- Execute and report the required controls with concrete validation evidence. Return `COMPLETE` only when they pass; otherwise return `WAITING` or `FAILED` with a concise safe diagnostic. +- Do not create a pull request. First publication is a later host-owned gate after both mandatory reviews pass. """ try: result = self._invoke_agent_with_timing( @@ -1700,39 +1741,25 @@ def _run_local_repository_validation( self.console_detail = error.console_detail validation = self._record_local_validation_audit(validation, result=None, outcome="agent_failed", profile=profile) return self._terminalize_provider_invocation_error(validation, error), implementation - if iteration > 1: - validation = self._record_repair_audit( - validation, failed_checks="validation: required local control", objective="Repair bounded local validation findings.", - result=result, - outcome="agent_failed" if result.terminal_state in {"BLOCKED", "FAILED"} else "submitted_for_recheck", - ) - if result.terminal_state in {"BLOCKED", "FAILED"}: - if ( - result.terminal_state == "FAILED" - and not self._is_external_agent_block(result) - and self._has_failed_validation_evidence(result) - ): - 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( - validation, - "BLOCKED", - "validation_infrastructure_recovery_required", - "Required local validation is unstable: a failed required suite and a passing isolated rerun were recorded without an implementation correction. Preserve this run and create a separate validation-infrastructure recovery item.", - ), implementation - continue + # A failed validation command is evidence for the shared repair + # route, not an unavailable validator. Only a provider-blocked + # invocation is terminal at this read-only gate. + if result.terminal_state == "BLOCKED": validation = self._record_local_validation_audit(validation, result=result, outcome="agent_failed", profile=profile) - return self._save_terminal(validation, result.terminal_state, "local_repository_validation_failed", result.diagnostic or "Local repository validation failed."), implementation + return validation, result if result.branch and result.branch != branch: validation = self._record_local_validation_audit(validation, result=result, outcome="agent_failed", profile=profile) return self._save_terminal(validation, "BLOCKED", "local_validation_scope", "Local validation changed the bounded implementation branch."), implementation if result.pull_request: + validation = self._record_local_validation_audit(validation, result=result, outcome="validated", profile=profile) + return self._save_terminal( + 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): validation = self._record_local_validation_audit(validation, result=result, outcome="validated", profile=profile) return validation, replace( - result, - branch=branch, + result, branch=branch, pull_request=None, validation_evidence=implementation.validation_evidence + result.validation_evidence, ) validation = self._record_local_validation_audit(validation, result=result, outcome="validation_failed", profile=profile) @@ -1743,7 +1770,7 @@ def _run_local_repository_validation( "validation_infrastructure_recovery_required", "Required local validation is unstable: a failed required suite and a passing isolated rerun were recorded without an implementation correction. Preserve this run and create a separate validation-infrastructure recovery item.", ), implementation - return self._save_terminal(validation, "BLOCKED", "local_validation_attempt_limit_reached", "Required local repository validation did not pass after 3 bounded iterations."), implementation + return validation, result def _run_quality_assurance( self, state: TransactionState, implementation: AgentResult, *, assurance_root: Path | None = None, @@ -1770,11 +1797,16 @@ def _run_quality_assurance( if not candidate.clean or not re.fullmatch(r"[0-9a-f]{40}", candidate.head_sha): return self._save_terminal(quality, "BLOCKED", "assurance_candidate_invalid", "Quality assurance requires one clean, pinned candidate."), implementation 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() + # 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( - f"{profile_version}:{candidate.head_sha}".encode("utf-8") + json.dumps({"baseline": profile_version, "criteria_digest": criteria_digest}, sort_keys=True).encode("utf-8") ).hexdigest() quality = replace(quality, assurance_profile={ "version": profile_version, "digest": profile_digest, "candidate_sha": candidate.head_sha, + "criteria_digest": criteria_digest, }) self.store.save(quality) write_live_status(self.root, quality, quality.next_action) @@ -1792,36 +1824,74 @@ def _run_quality_assurance( f"candidate {candidate.head_sha}. Report only concrete, bounded findings against the action acceptance criteria. " + Path(quality.prompt_path).read_text(encoding="utf-8") ) + started_at = datetime.now(timezone.utc).isoformat() result = run_reviews(assurance_root or self.root, (selection,), assurance_objective, self.agent if hasattr(self.agent, "review") else None, evidence=evidence)[0] + completed_at = datetime.now(timezone.utc).isoformat() try: unchanged = self._inspect_assurance_candidate(candidate_root, state.execution_mode) except RunnerError: unchanged = None - status = "UNRESOLVED" if result.failed or unchanged is None or unchanged.head_sha != candidate.head_sha else ("FAIL" if result.recommendations else "PASS") + supplied_findings = mandatory_findings(result) + status = "UNRESOLVED" if supplied_findings is None or unchanged is None or not unchanged.clean or unchanged.head_sha != candidate.head_sha else "PASS" findings = [ { - "id": f"{selection.reviewer}-{index + 1}", "fingerprint": hashlib.sha256(note.encode("utf-8")).hexdigest()[:32], - "category": selection.reviewer.upper(), "criterion": "post_implementation_assurance", - "observation": redact_diagnostic(note, limit=240), "severity": "HIGH", "confidence": "MEDIUM", - "blocking": True, "disposition": "OPEN", + "id": f"{quality.run_id}:{selection.reviewer}:{len(quality.assurance_reviews) + len(records) + 1}:{item['id']}", + "fingerprint": hashlib.sha256(json.dumps(item, sort_keys=True).encode("utf-8")).hexdigest()[:32], + "category": item["category"], "criterion": item["criterion"], + "observation": item["observation"], "severity": item["severity"], "confidence": item["confidence"], + "blocking": item["severity"] in {"HIGH", "CRITICAL"}, + "disposition": "OPEN" if item["severity"] in {"HIGH", "CRITICAL"} else "NON_BLOCKING", + "evidence_ref": item["evidence_ref"], } - for index, note in enumerate(result.recommendations[:3]) + for item in (supplied_findings or ()) ] + if status == "PASS" and any(finding["blocking"] for finding in findings): + status = "FAIL" records.append({ "reviewer": selection.reviewer, "status": status, "candidate_sha": candidate.head_sha, "profile_digest": profile_digest, "invocation_id": f"{quality.run_id}:{selection.reviewer}:{len(quality.assurance_reviews) + len(records)}", - "findings": findings, + "findings": findings, "contract_version": MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, + "started_at": started_at, "completed_at": completed_at, }) quality = replace(quality, assurance_reviews=quality.assurance_reviews + tuple(records)) + # A later PASS does not erase an earlier blocker. Only a completed + # reserved repair followed by this exact re-review can append the + # linked resolution evidence; the original finding remains immutable. + if all(record["status"] == "PASS" for record in records) and quality.repair_audit: + repair = quality.repair_audit[-1] + if repair.get("outcome") == "submitted_for_recheck" and repair.get("repair_id"): + resolved = {item["finding_id"] for item in quality.assurance_resolutions} + prior_blockers = [ + finding for review in quality.assurance_reviews[:-len(records)] + for finding in review.get("findings", []) + if isinstance(finding, dict) and finding.get("blocking") and finding.get("disposition") == "OPEN" + and isinstance(finding.get("id"), str) and finding["id"] not in resolved + ] + if prior_blockers: + review_refs = ",".join(str(record["invocation_id"]) for record in records) + resolutions = tuple({ + "finding_id": str(finding["id"]), "disposition": "RESOLVED", + "resolution_ref": f"{repair['repair_id']}|{review_refs}", + "candidate_sha": candidate.head_sha, + } for finding in prior_blockers) + quality = replace(quality, assurance_resolutions=quality.assurance_resolutions + resolutions) self.store.save(quality) unresolved = [record for record in records if record["status"] == "UNRESOLVED"] if unresolved: return self._save_terminal(quality, "BLOCKED", "mandatory_assurance_unresolved", "A required quality or security review was unavailable, malformed, or candidate-mismatched."), implementation findings = [finding for record in records for finding in record["findings"]] - if findings: + blockers = [finding for finding in findings if finding["blocking"] and finding["disposition"] == "OPEN"] + if blockers: if quality.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: return self._save_terminal(quality, "BLOCKED", "repair_budget_exhausted", "Mandatory assurance blockers remain after the run-wide repair budget was exhausted."), implementation - repaired = self._repair(quality, "quality/security review findings failed. Repair all listed bounded findings in one implementation repair round.") + blocker_roles = sorted({str(record["reviewer"]) for record in records if any( + finding["blocking"] and finding["disposition"] == "OPEN" for finding in record["findings"] + )}) + summary = "; ".join( + f"{finding['id']}: {finding['criterion']} — {finding['observation']}" + for finding in blockers + ) + repaired = self._repair(quality, f"{'/'.join(blocker_roles)} review findings failed. Repair these bounded findings: {summary}") return repaired, implementation return quality, implementation @@ -1848,6 +1918,85 @@ def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> Repos raise RunnerError(str(error)) from error return RepositoryEvidence(root.name, branch, head_sha, clean, True) + @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) + ) + + def _publish_first_implementation_pull_request( + self, state: TransactionState, implementation: AgentResult, + ) -> tuple[TransactionState, AgentResult]: + """Publish exactly one new Managed implementation PR after assurance. + + This is deliberately a host gate rather than a sentence attached to a + previous provider prompt. The provider receives no authority to edit + 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: + return state, 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: + before = self.repository.inspect(self.root) + except RunnerError: + return self._save_terminal(state, "BLOCKED", "implementation_publication_candidate_unavailable", "Reviewed Managed candidate is unavailable for PR publication."), implementation + profile = state.assurance_profile or {} + if not before.clean or before.branch != implementation.branch or before.head_sha != profile.get("candidate_sha"): + 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) + """ + +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 + try: + after = self.repository.inspect(self.root) + except RunnerError: + after = None + failures = [] + if published.terminal_state != "COMPLETE": failures.append("provider_not_complete") + 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") + 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") + elif after.head_sha != before.head_sha: failures.append("candidate_changed_after_publication") + if failures: + return self._save_terminal( + publication, "BLOCKED", "implementation_publication_evidence_invalid", + "First implementation PR publication changed or failed to identify the reviewed candidate: " + ", ".join(failures) + ".", + ), implementation + return publication, published + def _reject_historical_agent_pull_request( self, state: TransactionState ) -> TransactionState | None: @@ -1912,11 +2061,19 @@ def _advance_after_primary_agent_result( state, result = self._run_local_repository_validation(state, result) if state.terminal: return state + if result.terminal_state != "COMPLETE" or self._has_failed_validation_evidence(result): + if state.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: + return self._save_terminal(state, "BLOCKED", "repair_budget_exhausted", "Local validation requires a repair after the run-wide repair budget was exhausted.") + return self._repair(state, "local validation failed. Repair the recorded validation findings for the current candidate.") state, result = self._run_quality_assurance(state, result) if state.terminal: return state if state.phase in {"REPAIR_AGENT", "WAIT_FOR_TERMINAL_EVIDENCE", "WAIT_FOR_OPERATOR_MERGE"}: return state + if state.owner_authorized: + state, result = self._publish_first_implementation_pull_request(state, result) + if state.terminal: + return state return self._continue_after_quality_control(state, result, evidence) def _continue_after_quality_control( @@ -2786,7 +2943,14 @@ def _poll(self, state: TransactionState, result: AgentResult | None = None) -> T return self._save_operator_merge_wait(waiting) def _repair(self, state: TransactionState, objective: str) -> TransactionState: + if state.repair_iterations >= MAX_TOTAL_REPAIR_ROUNDS_PER_RUN: + return self._save_terminal( + state, "BLOCKED", "repair_budget_exhausted", + "The run-wide maximum of three correction rounds has been consumed.", + ) failed_checks = objective.split(" failed.", 1)[0] + origin = next((origin for origin in ("validation", "quality", "security", "hosted", "finalization") if origin in objective.casefold()), "validation") + input_candidate = state.last_verified_sha or (state.assurance_profile or {}).get("candidate_sha") or "not_recorded" repair = replace( state, phase="REPAIR_AGENT", @@ -2799,6 +2963,14 @@ def _repair(self, state: TransactionState, objective: str) -> TransactionState: repair = self._record_repair_audit( repair, failed_checks=failed_checks, objective=objective, result=None, outcome="planned", ) + reservation = dict(repair.repair_audit[-1]) + reservation.update({ + "repair_id": f"repair:{repair.run_id}:{repair.repair_iterations}", + "origin": origin, + "input_candidate_sha": input_candidate, + "dispatch_id": f"{repair.run_id}:repair:{repair.repair_iterations}", + }) + repair = replace(repair, repair_audit=repair.repair_audit[:-1] + (reservation,)) self.store.save(repair) write_live_status(self.root, repair, repair.next_action) try: diff --git a/src/engineering_platform/qualification_runtime.py b/src/engineering_platform/qualification_runtime.py index 9b67d455..b143d237 100644 --- a/src/engineering_platform/qualification_runtime.py +++ b/src/engineering_platform/qualification_runtime.py @@ -12,7 +12,7 @@ import subprocess import time -from .capability_review import ReviewerResult +from .capability_review import MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, ReviewerResult from .execution_models import AgentResult, PullRequestEvidence @@ -54,15 +54,26 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: sha = subprocess.run(("git", "-C", str(target_root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() return AgentResult("COMPLETE", terminal_condition="local_commit_reconciled", repository_path=str(target_root), commit_sha=sha) is_finalization = "finalization pr on exactly" in prompt.lower() - if self._github_write_target(root): + publication = "first implementation pull-request publication gate" in prompt.lower() + if self._github_write_target(root) and (is_finalization or publication): return self._create_github_managed_handoff( root, finalization=is_finalization, + publication=publication, prompt=prompt, ) + if not is_finalization and not publication: + # The implementation candidate is a real local branch before it + # is reviewed, but its remote PR is deliberately absent until the + # host's first-publication gate. + current = subprocess.run(("git", "-C", str(root), "branch", "--show-current"), check=True, text=True, capture_output=True).stdout.strip() + if current != "qualification-managed": + subprocess.run(("git", "-C", str(root), "switch", "-c", "qualification-managed"), check=True, text=True, capture_output=True) + if self._github_write_target(root): + subprocess.run(("git", "-C", str(root), "push", "--set-upstream", "origin", "qualification-managed"), check=True, text=True, capture_output=True) sha = subprocess.run(("git", "-C", str(root), "rev-parse", "HEAD"), check=True, text=True, capture_output=True).stdout.strip() branch = self._finalization_branch(prompt) if is_finalization else "qualification-managed" - return AgentResult("COMPLETE", branch=branch, pull_request=1, commit_sha=sha) + return AgentResult("COMPLETE", branch=branch, pull_request=1 if (is_finalization or publication) else None, commit_sha=sha) @staticmethod def _finalization_branch(prompt: str) -> str: @@ -89,7 +100,7 @@ def _github_write_target(root: Path) -> bool: @staticmethod def _create_github_managed_handoff( - root: Path, *, finalization: bool = False, prompt: str = "" + root: Path, *, finalization: bool = False, publication: bool = False, prompt: str = "" ) -> AgentResult: """Create one bounded dummy-repository branch and GitHub PR. @@ -116,17 +127,20 @@ def _create_github_managed_handoff( def run(*args: str) -> str: return subprocess.run(args, check=True, text=True, capture_output=True).stdout.strip() # nosec B603 - run("git", "-C", str(root), "switch", "-c", branch) - proof = root / ".engineering-platform" / ("managed-github-e2e-finalization-proof.json" if finalization else "managed-github-e2e-proof.json") - proof.write_text(json.dumps({ - "branch": branch, - "kind": "EP_MANAGED_GITHUB_E2E", - "stage": "FINALIZATION" if finalization else "IMPLEMENTATION", - "version": 1, - }, sort_keys=True) + "\n", encoding="utf-8") - run("git", "-C", str(root), "add", str(proof.relative_to(root))) - run("git", "-C", str(root), "commit", "-m", "test: record managed GitHub qualification handoff") - run("git", "-C", str(root), "push", "--set-upstream", "origin", branch) + existing = run("git", "-C", str(root), "branch", "--show-current") + if existing != branch: + run("git", "-C", str(root), "switch", "-c", branch) + if not publication: + proof = root / ".engineering-platform" / ("managed-github-e2e-finalization-proof.json" if finalization else "managed-github-e2e-proof.json") + proof.write_text(json.dumps({ + "branch": branch, + "kind": "EP_MANAGED_GITHUB_E2E", + "stage": "FINALIZATION" if finalization else "IMPLEMENTATION", + "version": 1, + }, sort_keys=True) + "\n", encoding="utf-8") + run("git", "-C", str(root), "add", str(proof.relative_to(root))) + run("git", "-C", str(root), "commit", "-m", "test: record managed GitHub qualification handoff") + run("git", "-C", str(root), "push", "--set-upstream", "origin", branch) title = "test: managed GitHub qualification finalization" if finalization else "test: managed GitHub qualification" run("gh", "pr", "create", "--repo", repository, "--head", branch, "--base", "main", "--title", title, "--body", "Explicitly authorized Engineering Platform dummy-repository qualification.") number = int(run("gh", "pr", "view", branch, "--repo", repository, "--json", "number", "--jq", ".number")) @@ -137,8 +151,24 @@ def available(self) -> bool: return True # Keep the public provider-version contract valid so the normal installed # compatibility gate remains part of qualification. def version(self) -> str: return "0.153.4" + 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. + """ + 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() + return AgentResult( + "COMPLETE", branch=branch, commit_sha=sha, + validation_evidence=({"command": "deterministic installed validation", "result": "passed"},), + ) def review(self, _root: Path, selection: object, _objective: str, evidence: object = None) -> ReviewerResult: - return ReviewerResult(getattr(selection, "reviewer"), "Deterministic read-only assurance passed.") + return ReviewerResult( + getattr(selection, "reviewer"), "Deterministic read-only assurance passed.", + findings=(), contract_version=MANDATORY_REVIEW_OUTPUT_CONTRACT_VERSION, + ) class LocalQualificationGitHub: diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index f99a3675..340f7077 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -409,6 +409,65 @@ def _findings_artifact_id(run_id: str) -> str: return f"assurance-findings:{run_id}" +def _current_assurance(checkpoint: object) -> tuple[str, list[dict[str, object]], list[dict[str, object]]]: + """Project final assurance from one complete current review set. + + Earlier review records are deliberately not overwritten: they remain + historical observations in the findings artifact. Only the most recent + complete quality/security pair bound to the checkpoint's exact candidate + and policy can qualify terminal delivery. + """ + profile = getattr(checkpoint, "assurance_profile", None) + reviews = list(getattr(checkpoint, "assurance_reviews", ())) + if not isinstance(profile, dict): + return "NOT_RECORDED", [], reviews + candidate, digest = profile.get("candidate_sha"), profile.get("digest") + if not isinstance(candidate, str) or not isinstance(digest, str): + return "UNRESOLVED", [], reviews + current = [ + review for review in reviews + if isinstance(review, dict) + and review.get("candidate_sha") == candidate + and review.get("profile_digest") == digest + ] + latest = { + role: next((review for review in reversed(current) if review.get("reviewer") == role), None) + for role in ("quality", "security") + } + if any(review is None or review.get("status") == "UNRESOLVED" for review in latest.values()): + return "UNRESOLVED", current, reviews + if any(review.get("status") != "PASS" for review in latest.values() if isinstance(review, dict)): + return "FAIL", current, reviews + findings = [finding for review in latest.values() if isinstance(review, dict) for finding in review.get("findings", []) if isinstance(finding, dict)] + current_open = any(finding.get("blocking") and finding.get("disposition") == "OPEN" for finding in findings) + resolved = { + item.get("finding_id") for item in getattr(checkpoint, "assurance_resolutions", ()) + if isinstance(item, dict) and item.get("disposition") in {"RESOLVED", "REJECTED", "RISK_ACCEPTED"} + } + historical_open = any( + finding.get("blocking") and finding.get("disposition") == "OPEN" and finding.get("id") not in resolved + for review in reviews if isinstance(review, dict) + for finding in review.get("findings", []) if isinstance(finding, dict) + ) + return ("FAIL" if current_open or historical_open else "PASS"), current, reviews + + +def _write_immutable_artifact(target: Path, payload: bytes) -> None: + """Create immutable evidence once; conflicting terminal rewrites fail closed.""" + if target.exists(): + try: + if target.read_bytes() == payload: + return + except OSError: + pass + raise SubmissionError("TERMINAL_EVIDENCE_IMMUTABLE_CONFLICT", 500) + target.parent.mkdir(mode=0o700, parents=True, exist_ok=True) + temporary = target.with_suffix(target.suffix + ".tmp") + temporary.write_bytes(payload) + temporary.chmod(0o600) + temporary.replace(target) + + def _repository_revision(state: object, outcome: str) -> tuple[str | None, bool]: """Return a run-bound delivery revision, never an ambient checkout HEAD.""" if outcome != "COMPLETE": @@ -471,7 +530,7 @@ def write_terminal_evidence( revision, delivery_qualified = _repository_revision(checkpoint, outcome) artifact_id = _terminal_artifact_id(run_id) report_id = f"report:{run_id}" - reviews = list(checkpoint.assurance_reviews) + assurance_status, current_reviews, reviews = _current_assurance(checkpoint) findings = [finding for review in reviews for finding in review.get("findings", [])] findings_id = _findings_artifact_id(run_id) if checkpoint.assurance_profile is not None else None if findings_id is not None: @@ -479,11 +538,11 @@ def write_terminal_evidence( "artifact_type": "EP_ASSURANCE_FINDINGS", "contract_version": "1.0", "run_id": run_id, "project_id": str(row[1]), "repository_id": str(row[2]), "profile": checkpoint.assurance_profile, "reviews": reviews, + "resolutions": list(checkpoint.assurance_resolutions), } findings_target = data_root / "artifacts" / "projects" / str(row[1]) / "runs" / run_id / "assurance-findings-v1.json" - findings_target.parent.mkdir(mode=0o700, parents=True, exist_ok=True) - findings_target.write_bytes(_canonical_json_bytes(findings_payload)) - findings_target.chmod(0o600) + findings_bytes = _canonical_json_bytes(findings_payload) + _write_immutable_artifact(findings_target, findings_bytes) record_artifact(repository_root, findings_target, artifact_id=findings_id, artifact_type="EP_ASSURANCE_FINDINGS", content_type="application/json", created_at=_now(), run_id=run_id, submission_id=str(row[0]), mission_id=str(row[10]) if row[10] is not None else None, producer_id=str(row[4]), @@ -504,20 +563,16 @@ def write_terminal_evidence( "repair": list(checkpoint.repair_audit), "finalization": checkpoint.latest_repository_evidence, }, "assurance": { - "status": "NOT_RECORDED" if checkpoint.assurance_profile is None else ("PASS" if all(review.get("status") == "PASS" for review in reviews) else "UNRESOLVED" if any(review.get("status") == "UNRESOLVED" for review in reviews) else "FAIL"), + "status": assurance_status, "profile": checkpoint.assurance_profile, - "quality_review": next((review.get("status") for review in reversed(reviews) if review.get("reviewer") == "quality"), "NOT_RECORDED"), - "security_review": next((review.get("status") for review in reversed(reviews) if review.get("reviewer") == "security"), "NOT_RECORDED"), + "quality_review": next((review.get("status") for review in reversed(current_reviews) if review.get("reviewer") == "quality"), "NOT_RECORDED"), + "security_review": next((review.get("status") for review in reversed(current_reviews) if review.get("reviewer") == "security"), "NOT_RECORDED"), "repair_rounds": {"used": checkpoint.repair_iterations, "maximum": 3}, - "findings": {"open_blocking": sum(1 for finding in findings if finding.get("blocking") and finding.get("disposition") == "OPEN"), "open_non_blocking": sum(1 for finding in findings if not finding.get("blocking") and finding.get("disposition") == "OPEN"), "artifact": None if findings_id is None else {"id": findings_id}}, + "findings": {"open_blocking": sum(1 for review in current_reviews for finding in review.get("findings", []) if finding.get("blocking") and finding.get("disposition") == "OPEN"), "open_non_blocking": sum(1 for review in current_reviews for finding in review.get("findings", []) if not finding.get("blocking") and finding.get("disposition") in {"OPEN", "NON_BLOCKING"}), "artifact": None if findings_id is None else {"id": findings_id, "digest_algorithm": "sha256", "digest": hashlib.sha256(findings_bytes).hexdigest()}}, }, } target = data_root / "artifacts" / "projects" / str(row[1]) / "runs" / run_id / "terminal-evidence-v1.json" - target.parent.mkdir(mode=0o700, parents=True, exist_ok=True) - temporary = target.with_suffix(".json.tmp") - temporary.write_bytes(_canonical_json_bytes(payload)) - temporary.chmod(0o600) - temporary.replace(target) + _write_immutable_artifact(target, _canonical_json_bytes(payload)) record_artifact( repository_root, target, artifact_id=artifact_id, artifact_type="EP_TERMINAL_EVIDENCE", content_type="application/json", created_at=_now(), run_id=run_id, diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 6259be18..501488f5 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -429,6 +429,11 @@ 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: + return AgentResult( + "COMPLETE", branch, commit_sha=commit, + validation_evidence=({"command": "canonical suite", "result": "passed"},), + ) + if "First implementation pull-request publication gate" in prompt_text: self.pr_create_calls += 1 return AgentResult("COMPLETE", branch, 701, commit_sha=commit) if "Mandatory autonomous refactor" in prompt_text: @@ -1206,7 +1211,7 @@ def github(self, *_: str) -> str: @patch("engineering_platform.execution_host.subprocess.run") def test_codex_client_handles_valid_review_and_invoke_results(self, run: object) -> None: review_message = json.dumps( - {"contribution": "reviewed", "recommendations": ["keep scope"]} + {"contract_version": "1.0", "contribution": "reviewed", "recommendations": ["keep scope"], "findings": []} ) agent_message = json.dumps( { @@ -1227,17 +1232,22 @@ def test_codex_client_handles_valid_review_and_invoke_results(self, run: object) run.side_effect = [ subprocess.CompletedProcess(("codex",), 0, review_output, ""), subprocess.CompletedProcess(("codex",), 0, json.dumps({"type": "item.completed", "item": {"type": "agent_message", "text": agent_message}}), ""), + subprocess.CompletedProcess(("codex",), 0, json.dumps({"type": "item.completed", "item": {"type": "agent_message", "text": agent_message}}), ""), ] with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) client = CodexCliClient(CodexCliProvider()) review = client.review(root, __import__("engineering_platform.capability_review", fromlist=["ReviewerSelection"]).ReviewerSelection("validation", "scope", 1), "objective") result = client.invoke(root, "objective") + validation = client.validate(root, "validation objective") self.assertFalse(review.failed) self.assertEqual(review.recommendations, ("keep scope",)) self.assertEqual(review.runtime_metadata["raw_provider_model"], "gpt-5.6-terra") self.assertEqual(review.usage["input_tokens"], 100) self.assertEqual(result.pull_request, 12) + self.assertEqual(validation.pull_request, 12) + self.assertIn("read-only", run.call_args_list[2].args[0]) + self.assertFalse(hasattr(client, "_sandbox_override")) @patch("engineering_platform.execution_host.time.monotonic", side_effect=(10.0, 12.75)) @patch("engineering_platform.execution_host.subprocess.run") @@ -2366,13 +2376,11 @@ def test_local_repository_validation_iterates_before_creating_the_implementation state, AgentResult("COMPLETE", "codex/implementation") ) self.assertEqual(validated.phase, "LOCAL_REPOSITORY_VALIDATION") - self.assertEqual(validated.local_validation_iterations, 2) - self.assertEqual([item["outcome"] for item in validated.local_validation_audit], ["validation_failed", "validated"]) - self.assertEqual(validated.local_validation_audit[0]["proposed_action"], "FULL: full required repository suite") - self.assertEqual(validated.validation_evidence, ({"command": "python -m unittest tests.engineering", "result": "passed"},)) - self.assertEqual(result.pull_request, 701) - self.assertIn("iteration 1 of 3", agent.prompts[0]) - self.assertIn("Create one draft implementation pull request only after", agent.prompts[0]) + self.assertEqual(validated.local_validation_iterations, 1) + self.assertEqual([item["outcome"] for item in validated.local_validation_audit], ["validation_failed"]) + self.assertIsNone(result.pull_request) + self.assertIn("read-only", agent.prompts[0]) + self.assertNotIn("draft implementation pull request", agent.prompts[0]) def test_verified_implementation_validation_failure_enters_local_repair_route(self) -> None: sha = "b" * 40 @@ -2402,9 +2410,9 @@ def test_verified_implementation_validation_failure_enters_local_repair_route(se validated, result = runner._run_local_repository_validation(verified, initial) self.assertFalse(validated.terminal) - self.assertEqual(validated.local_validation_iterations, 2) - self.assertEqual([item["outcome"] for item in validated.local_validation_audit], ["validation_failed", "validated"]) - self.assertEqual(result.pull_request, 701) + self.assertEqual(validated.local_validation_iterations, 1) + self.assertEqual([item["outcome"] for item in validated.local_validation_audit], ["validation_failed"]) + self.assertIsNone(result.pull_request) def test_runner_routes_verified_failed_implementation_to_local_validation_before_pr(self) -> None: sha = "d" * 40 @@ -2440,12 +2448,13 @@ def invoke(self, root: Path, prompt: str) -> AgentResult: ) self.assertTrue(state.commit_evidence, state.diagnostic) - self.assertEqual(state.phase, "WAIT_FOR_OPERATOR_MERGE", state.diagnostic) - self.assertFalse(state.terminal) + self.assertEqual(state.phase, "BLOCKED", state.diagnostic) + self.assertTrue(state.terminal) + self.assertEqual(state.next_action, "implementation_pr_before_assurance") self.assertEqual(state.local_validation_iterations, 1) self.assertEqual(state.local_validation_audit[0]["outcome"], "validated") self.assertEqual(len(agent.prompts), 2) - self.assertIn("Local repository validation gate — iteration 1 of 3", agent.prompts[1]) + self.assertIn("Local repository validation gate", agent.prompts[1]) def test_unverified_or_external_implementation_failure_never_starts_local_repair(self) -> None: sha = "c" * 40 @@ -2462,7 +2471,7 @@ def test_unverified_or_external_implementation_failure_never_starts_local_repair self.assertFalse(runner._is_recoverable_implementation_validation_failure(state, result)) - def test_failed_local_validation_uses_all_three_bounded_attempts(self) -> None: + def test_failed_local_validation_is_one_non_mutating_turn_before_shared_repair(self) -> None: failures = [ AgentResult( "FAILED", "codex/implementation", diagnostic="Required local suite failed.", @@ -2482,10 +2491,9 @@ def test_failed_local_validation_uses_all_three_bounded_attempts(self) -> None: state, AgentResult("COMPLETE", "codex/implementation") ) - self.assertTrue(blocked.terminal) - self.assertEqual(blocked.next_action, "local_validation_attempt_limit_reached") - self.assertEqual(blocked.local_validation_iterations, 3) - self.assertEqual(len(blocked.local_validation_audit), 3) + self.assertFalse(blocked.terminal) + self.assertEqual(blocked.local_validation_iterations, 1) + self.assertEqual(len(blocked.local_validation_audit), 1) self.assertEqual({item["outcome"] for item in blocked.local_validation_audit}, {"validation_failed"}) def test_local_repository_validation_separates_proven_environment_instability(self) -> None: diff --git a/tests/engineering/test_qualification_runtime.py b/tests/engineering/test_qualification_runtime.py index b5c1a1cb..f72fce83 100644 --- a/tests/engineering/test_qualification_runtime.py +++ b/tests/engineering/test_qualification_runtime.py @@ -36,12 +36,14 @@ def test_local_genesis_managed_and_reconciliation_results_are_deterministic(self agent.set_process_callback(process.append) managed = agent.invoke(self.root, "implement the approved work") + publication = agent.invoke(self.root, "First implementation pull-request publication gate") reconciled = agent.invoke(self.root, "the sole automatic post-finalization reconciliation") genesis = agent.invoke(self.root, f"Execution mode: Genesis\nTarget repository: {self.root}") self.assertEqual(process[0]["pid"], os.getpid()) self.assertEqual(managed.branch, "qualification-managed") - self.assertEqual(managed.pull_request, 1) + self.assertIsNone(managed.pull_request) + self.assertEqual(publication.pull_request, 1) self.assertEqual(reconciled.terminal_condition, "repository_reconciled") self.assertEqual(genesis.terminal_condition, "local_commit_reconciled") self.assertEqual(genesis.repository_path, str(self.root.resolve())) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 947377f4..bcdf27e7 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -257,6 +257,35 @@ def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evid self.assertFalse(corrupt["result"]["delivery_qualified"]) # type: ignore[index] self.assertIsNone(submission_service.producer_evidence_artifact(connection, project_id="djconnect", artifact_id=artifact_id)) + def test_terminal_assurance_requires_a_complete_current_pair_and_explicit_historical_resolution(self) -> None: + """A missing/stale review cannot become PASS through empty aggregation.""" + candidate, digest = "c" * 40, "sha256:" + "b" * 64 + profile = {"version": "validation-profile@1", "digest": digest, "candidate_sha": candidate} + quality = {"reviewer": "quality", "status": "PASS", "candidate_sha": candidate, + "profile_digest": digest, "invocation_id": "quality-current", "findings": []} + security = {"reviewer": "security", "status": "PASS", "candidate_sha": candidate, + "profile_digest": digest, "invocation_id": "security-current", "findings": []} + base = dict(run_id="assurance-current", repository="djconnect", prompt_path="prompt", + phase="COMPLETE", terminal=True, assurance_profile=profile) + self.assertEqual(submission_service._current_assurance(TransactionState(**base))[0], "UNRESOLVED") + self.assertEqual(submission_service._current_assurance(TransactionState(**base, assurance_reviews=(quality,)))[0], "UNRESOLVED") + stale = {**security, "candidate_sha": "d" * 40} + self.assertEqual(submission_service._current_assurance(TransactionState(**base, assurance_reviews=(quality, stale)))[0], "UNRESOLVED") + + old_finding = {"id": "old-security", "fingerprint": "e" * 32, "category": "SECURITY", + "criterion": "isolation", "observation": "Missing denial test", "severity": "HIGH", + "confidence": "HIGH", "blocking": True, "disposition": "OPEN"} + old_security = {**security, "status": "FAIL", "invocation_id": "security-old", "findings": [old_finding]} + unresolved = TransactionState(**base, assurance_reviews=(old_security, quality, security)) + self.assertEqual(submission_service._current_assurance(unresolved)[0], "FAIL") + resolved = TransactionState( + **base, assurance_reviews=(old_security, quality, security), + assurance_resolutions=({"finding_id": "old-security", "disposition": "RESOLVED", + "resolution_ref": "repair:assurance-current:1|quality-current,security-current", + "candidate_sha": candidate},), + ) + self.assertEqual(submission_service._current_assurance(resolved)[0], "PASS") + def test_forge_provenance_is_required_and_part_of_idempotency_identity(self) -> None: payload = self.payload("forge-replay") payload.update({"producer": {"id": "forge", "type": "FORGE", "version": "1.0"}, diff --git a/tools/qualification/p_deterministic_execution_e2e.py b/tools/qualification/p_deterministic_execution_e2e.py index 098bd9b8..b16e6b62 100644 --- a/tools/qualification/p_deterministic_execution_e2e.py +++ b/tools/qualification/p_deterministic_execution_e2e.py @@ -284,14 +284,12 @@ def main(argv: list[str] | None = None) -> int: parser.add_argument("--managed-repository", type=Path, help="Clean checkout of the approved dummy GitHub repository.") parser.add_argument("--managed-github-repository", help="Exact approved dummy GitHub owner/repository identity.") parser.add_argument("--allow-managed-github-writes", action="store_true", help="Explicitly authorize one dummy-repository branch and pull request.") - parser.add_argument("--persistent-root", type=Path, help="New isolated qualification root retained after an external GitHub hand-off.") + parser.add_argument("--persistent-root", type=Path, help="New isolated qualification root retained for inspection; it never shares canonical production data.") parser.add_argument("--bind-port", type=int, help="Fixed localhost port for a retained qualification Server.") args = parser.parse_args(argv) github_write = args.allow_managed_github_writes or args.managed_github_repository is not None if github_write and (not args.allow_managed_github_writes or not args.managed_repository or not args.managed_github_repository): raise RuntimeError("MANAGED_GITHUB_WRITE_AUTHORIZATION_REQUIRED") - if args.persistent_root and not github_write: - raise RuntimeError("PERSISTENT_QUALIFICATION_REQUIRES_GITHUB_WRITE_PROFILE") if args.persistent_root and args.persistent_root.exists() and any(args.persistent_root.iterdir()): raise RuntimeError("PERSISTENT_QUALIFICATION_ROOT_MUST_BE_EMPTY") context = nullcontext(str(args.persistent_root.resolve())) if args.persistent_root else tempfile.TemporaryDirectory(prefix="ep-deterministic-e2e-") From 1357f21a6895b027d47f3ae8be69b2fe4dd2764b Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 11:14:17 +0200 Subject: [PATCH 54/87] test: cover assurance publication and malformed review gates --- tests/engineering/test_execution_host.py | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 501488f5..2105ea4d 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -2356,6 +2356,37 @@ def test_quality_assurance_does_not_create_or_replace_the_implementation_pr(self self.assertEqual(len(agent.prompts), 1) self.assertEqual([review["status"] for review in state.assurance_reviews], ["PASS", "PASS"]) + 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 + 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"], "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) + state = TransactionState("publication-gate", "pcvantol/djconnect", str(self.prompt), "QUALITY_CONTROL_AGENT", + branch="main", owner_authorized=True, assurance_profile=profile, assurance_reviews=reviews) + published, result = runner._publish_first_implementation_pull_request( + state, AgentResult("COMPLETE", "main", commit_sha=sha) + ) + self.assertFalse(published.terminal) + self.assertEqual(result.pull_request, 71) + self.assertIn("First implementation pull-request publication gate", agent.prompts[0]) + + def test_malformed_mandatory_review_is_unresolved_not_an_empty_pass(self) -> None: + class MalformedReviewer(FakeAgent): + def review(self, _: Path, selection: object, __: str, evidence: object = None) -> ReviewerResult: + return ReviewerResult(getattr(selection, "reviewer"), "malformed", contract_version=None) + + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), MalformedReviewer(AgentResult("COMPLETE")), lambda _: None) + state = TransactionState("malformed-review", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", branch="main") + blocked, _ = runner._run_quality_assurance(state, AgentResult("COMPLETE", "main", commit_sha="a" * 40)) + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "mandatory_assurance_unresolved") + def test_local_repository_validation_iterates_before_creating_the_implementation_pr(self) -> None: agent = SequencedFakeAgent([ AgentResult( From b23d247119b4232f2d11f8876e7dc6fee0297c5f Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 11:47:20 +0200 Subject: [PATCH 55/87] fix: revalidate bounded repairs on their existing PR --- src/engineering_platform/execution_host.py | 11 +++++------ tests/engineering/test_execution_host.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 3e82b903..80678e0c 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1667,12 +1667,11 @@ def _run_local_repository_validation( # A PR that predates this assurance contract is preserved as # immutable lineage evidence. A newly-started run cannot use a # provider-returned PR to skip the publication gate. - if state.implementation_pull_request == implementation.pull_request: - return state, implementation - return self._save_terminal( - state, "BLOCKED", "implementation_pr_before_assurance", - "A new Managed run returned an implementation pull request before local validation and mandatory assurance.", - ), implementation + if implementation.pull_request not in {state.implementation_pull_request, state.pull_request}: + return self._save_terminal( + state, "BLOCKED", "implementation_pr_before_assurance", + "A new Managed run returned an implementation pull request before local validation and mandatory assurance.", + ), implementation if not branch: return self._save_terminal( state, "BLOCKED", "local_validation_scope", "Implementation must return one branch and no pull request before local validation." diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 2105ea4d..200a0f20 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -2387,6 +2387,28 @@ def review(self, _: Path, selection: object, __: str, evidence: object = None) - self.assertTrue(blocked.terminal) self.assertEqual(blocked.next_action, "mandatory_assurance_unresolved") + def test_reserved_repair_revalidates_and_rereviews_before_returning_to_pr_evidence(self) -> None: + """One repair consumes one durable round and cannot bypass assurance.""" + sha = "a" * 40 + agent = SequencedFakeAgent([ + AgentResult("COMPLETE", "main", commit_sha=sha), + AgentResult("COMPLETE", "main", commit_sha=sha, + validation_evidence=({"command": "canonical suite", "result": "passed"},)), + ]) + github = FakeGitHub([PullRequestEvidence(71, "OPEN", True, True, head_branch="main", base_branch="main")]) + runner = EngineeringRunner(self.root, self.store, FakeRepository(), github, agent, lambda _: None) + state = TransactionState("repair-rereview", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", + branch="main", pull_request=71, owner_authorized=True) + advanced = runner._repair(state, "quality review findings failed. Repair these bounded findings: finding-1") + # The fake remote intentionally has no persisted PR binding; what this + # regression owns is the required repair -> validation -> review path. + self.assertNotEqual(advanced.phase, "REPAIR_AGENT") + self.assertEqual(advanced.repair_iterations, 1) + self.assertEqual(len(advanced.repair_audit), 1) + self.assertEqual(advanced.repair_audit[0]["repair_id"], "repair:repair-rereview:1") + self.assertEqual([item["status"] for item in advanced.assurance_reviews], ["PASS", "PASS"]) + self.assertIn("Local repository validation gate", agent.prompts[1]) + def test_local_repository_validation_iterates_before_creating_the_implementation_pr(self) -> None: agent = SequencedFakeAgent([ AgentResult( From c9cbe95e5205c8d2d4aa27703c7b9b0bb80704aa Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 11:50:50 +0200 Subject: [PATCH 56/87] fix: translate assurance lifecycle modal labels --- src/engineering_platform/assets/dashboard.js | 7 ++++++- .../assets/dashboard_locales.mjs | 5 +++++ tests/engineering/dashboard.spec.mjs | 14 +++++++++++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index c14a61a7..4f58ad37 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -106,6 +106,11 @@ function reviewerStatusLabel(value, fallback = t("format.not_available")) { const normalized = reviewerKey(raw) === "uitgevoerd" ? "completed" : reviewerKey(raw); return t(`reviewer.status.${normalized}`, {}, raw.replaceAll("_", " ")); } +function assuranceStatusLabel(value, fallback = t("format.not_available")) { + const raw = String(value || "").trim(); + if (!raw) return fallback; + return t(`lifecycle.assurance_status.${reviewerKey(raw)}`, {}, raw); +} function reviewerCapabilityLabel(value, fallback = t("format.not_available")) { const raw = String(value || "").trim(); return raw ? enumLabel(raw.toUpperCase(), raw) : fallback; @@ -2030,7 +2035,7 @@ function lifecycleAssuranceEvidence(step) { const findings = Array.isArray(review.findings) ? review.findings : []; const item = document.createElement("li"); const role = String(review.reviewer || ""); const status = String(review.status || "UNRESOLVED"); - item.append(Object.assign(document.createElement("strong"), { textContent: `${reviewerLabel(role, role)} · ${status}` })); + item.append(Object.assign(document.createElement("strong"), { textContent: `${reviewerLabel(role, role)} · ${assuranceStatusLabel(status, status)}` })); const summary = findings.map((finding) => String(finding?.observation || "").trim()).filter(Boolean).join("; "); item.append(Object.assign(document.createElement("span"), { textContent: summary || t("lifecycle.assurance_no_findings") })); list.append(item); diff --git a/src/engineering_platform/assets/dashboard_locales.mjs b/src/engineering_platform/assets/dashboard_locales.mjs index 99d0427f..9abc5b3c 100644 --- a/src/engineering_platform/assets/dashboard_locales.mjs +++ b/src/engineering_platform/assets/dashboard_locales.mjs @@ -3425,6 +3425,11 @@ Object.assign(DASHBOARD_MESSAGES.nl, {"telemetry.phase.quality_control":"Kwalite Object.assign(DASHBOARD_MESSAGES.de, {"telemetry.phase.quality_control":"Qualitätskontrolle"}); Object.assign(DASHBOARD_MESSAGES.fr, {"telemetry.phase.quality_control":"Contrôle qualité"}); Object.assign(DASHBOARD_MESSAGES.es, {"telemetry.phase.quality_control":"Control de calidad"}); +Object.assign(DASHBOARD_MESSAGES.en, {"telemetry.phase.quality_control_agent":"Autonomous quality control","reviewer.quality":"Quality review","reviewer.security":"Security review","lifecycle.assurance_status.pass":"Passed","lifecycle.assurance_status.fail":"Failed","lifecycle.assurance_status.unresolved":"Unresolved"}); +Object.assign(DASHBOARD_MESSAGES.nl, {"telemetry.phase.quality_control_agent":"Autonome kwaliteitscontrole","reviewer.quality":"Kwaliteitsreview","reviewer.security":"Beveiligingsreview","lifecycle.assurance_status.pass":"Geslaagd","lifecycle.assurance_status.fail":"Mislukt","lifecycle.assurance_status.unresolved":"Niet opgehelderd"}); +Object.assign(DASHBOARD_MESSAGES.de, {"telemetry.phase.quality_control_agent":"Autonome Qualitätskontrolle","reviewer.quality":"Qualitätsprüfung","reviewer.security":"Sicherheitsprüfung","lifecycle.assurance_status.pass":"Bestanden","lifecycle.assurance_status.fail":"Fehlgeschlagen","lifecycle.assurance_status.unresolved":"Ungeklärt"}); +Object.assign(DASHBOARD_MESSAGES.fr, {"telemetry.phase.quality_control_agent":"Contrôle qualité autonome","reviewer.quality":"Revue qualité","reviewer.security":"Revue de sécurité","lifecycle.assurance_status.pass":"Réussi","lifecycle.assurance_status.fail":"Échec","lifecycle.assurance_status.unresolved":"Non résolu"}); +Object.assign(DASHBOARD_MESSAGES.es, {"telemetry.phase.quality_control_agent":"Control de calidad autónomo","reviewer.quality":"Revisión de calidad","reviewer.security":"Revisión de seguridad","lifecycle.assurance_status.pass":"Correcto","lifecycle.assurance_status.fail":"Fallido","lifecycle.assurance_status.unresolved":"Sin resolver"}); Object.assign(DASHBOARD_MESSAGES.en, {"workspace_progress.primary_codex_commands":"{count} primary Codex commands executed","telemetry.phase.capability_review":"Capability review","lifecycle.step.reconcile_agent":"End reconciliation","lifecycle.step.wait_for_reconciliation_merge":"Reconciliation merge","operational.stale_run":"Execution no longer active","operational.waiting_for_operator_merge":"Implementation merge","operational.stale_host_ownership":"Execution Host ownership is no longer active; no execution is currently running."}); Object.assign(DASHBOARD_MESSAGES.nl, {"workspace_progress.primary_codex_commands":"{count} primaire Codex-opdrachten uitgevoerd","telemetry.phase.capability_review":"Specialistenreview","lifecycle.step.reconcile_agent":"Eind-reconciliatie","lifecycle.step.wait_for_reconciliation_merge":"Reconciliatie-merge","operational.stale_run":"Uitvoering niet meer actief","operational.waiting_for_operator_merge":"Implementatie-merge","operational.stale_host_ownership":"De uitvoeringseigenaar is niet meer actief; er draait momenteel geen uitvoering."}); Object.assign(DASHBOARD_MESSAGES.de, {"workspace_progress.primary_codex_commands":"{count} primäre Codex-Befehle ausgeführt","telemetry.phase.capability_review":"Fähigkeitsprüfung","lifecycle.step.reconcile_agent":"Endabgleich","lifecycle.step.wait_for_reconciliation_merge":"Abgleich-Merge","operational.stale_run":"Ausführung nicht mehr aktiv","operational.waiting_for_operator_merge":"Implementierungs-Merge","operational.stale_host_ownership":"Die Ausführungsinstanz ist nicht mehr aktiv; derzeit läuft keine Ausführung."}); diff --git a/tests/engineering/dashboard.spec.mjs b/tests/engineering/dashboard.spec.mjs index c0d04e84..89aaa61b 100644 --- a/tests/engineering/dashboard.spec.mjs +++ b/tests/engineering/dashboard.spec.mjs @@ -3290,8 +3290,12 @@ test.describe("Engineering Status browser smoke", () => { steps: [ { id: "execute", presentation_key: "lifecycle.step.execute_agent", state: "COMPLETED" }, { id: "quality", presentation_key: "lifecycle.step.quality_control_agent", state: "ACTIVE", - timing: { started_at: "2026-08-16T14:00:00Z", spans: [{ phase: "QUALITY_CONTROL", duration_ms: 1000, outcome: "ACTIVE" }] }, - quality_evidence: [{ activity: "TEST_COVERAGE", result: "Gerichte regressietest toegevoegd." }] }, + timing: { started_at: "2026-08-16T14:00:00Z", spans: [{ phase: "QUALITY_CONTROL_AGENT", duration_ms: 1000, outcome: "ACTIVE" }] }, + quality_evidence: [{ activity: "TEST_COVERAGE", result: "Gerichte regressietest toegevoegd." }], + assurance_reviews: [ + { reviewer: "quality", status: "PASS", findings: [] }, + { reviewer: "security", status: "PASS", findings: [] }, + ] }, ], }, }, {})); @@ -3302,10 +3306,14 @@ test.describe("Engineering Status browser smoke", () => { const modal = page.locator("#lifecycleDetailModal"); await expect(modal).toBeVisible(); await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["lifecycle.step.quality_control_agent"]); - await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["telemetry.phase.quality_control"]); + await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["telemetry.phase.quality_control_agent"]); await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["lifecycle.detail_quality_evidence"]); await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["lifecycle.quality_evidence.test_coverage"]); await expect(modal).toContainText("Gerichte regressietest toegevoegd."); + await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["reviewer.quality"]); + await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["reviewer.security"]); + await expect(modal).toContainText(DASHBOARD_MESSAGES.nl["lifecycle.assurance_status.pass"]); + await expect(modal).not.toContainText("QUALITY_CONTROL_AGENT"); await expect(modal.locator(".lifecycle-detail-modal__status-indicator")).toHaveClass(/indicator--blue/); }); From 07e7b838c21f2e59b9ba9553514b13ee370c8a4d Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 15:16:19 +0200 Subject: [PATCH 57/87] test: cover bounded repair revalidation lifecycle --- tests/engineering/test_execution_host.py | 25 ++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 200a0f20..c96f274b 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -2390,25 +2390,34 @@ def review(self, _: Path, selection: object, __: str, evidence: object = None) - def test_reserved_repair_revalidates_and_rereviews_before_returning_to_pr_evidence(self) -> None: """One repair consumes one durable round and cannot bypass assurance.""" sha = "a" * 40 + branch = "codex/repair-rereview" agent = SequencedFakeAgent([ - AgentResult("COMPLETE", "main", commit_sha=sha), - AgentResult("COMPLETE", "main", commit_sha=sha, + AgentResult("COMPLETE", branch, commit_sha=sha), + AgentResult("COMPLETE", branch, commit_sha=sha, validation_evidence=({"command": "canonical suite", "result": "passed"},)), ]) - github = FakeGitHub([PullRequestEvidence(71, "OPEN", True, True, head_branch="main", base_branch="main")]) - runner = EngineeringRunner(self.root, self.store, FakeRepository(), github, agent, lambda _: None) + 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) state = TransactionState("repair-rereview", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT", - branch="main", pull_request=71, owner_authorized=True) + branch=branch, pull_request=71, owner_authorized=True) advanced = runner._repair(state, "quality review findings failed. Repair these bounded findings: finding-1") - # The fake remote intentionally has no persisted PR binding; what this - # regression owns is the required repair -> validation -> review path. - self.assertNotEqual(advanced.phase, "REPAIR_AGENT") + self.assertEqual(advanced.phase, "WAIT_FOR_OPERATOR_MERGE") self.assertEqual(advanced.repair_iterations, 1) self.assertEqual(len(advanced.repair_audit), 1) self.assertEqual(advanced.repair_audit[0]["repair_id"], "repair:repair-rereview:1") self.assertEqual([item["status"] for item in advanced.assurance_reviews], ["PASS", "PASS"]) self.assertIn("Local repository validation gate", agent.prompts[1]) + def test_fourth_shared_repair_dispatch_is_refused_before_provider_invocation(self) -> None: + agent = FakeAgent(AgentResult("COMPLETE")) + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + exhausted = TransactionState("repair-exhausted", "pcvantol/djconnect", str(self.prompt), "REPAIR_AGENT", + branch="codex/repair", repair_iterations=3) + blocked = runner._repair(exhausted, "hosted check failed. Repair bounded finding.") + self.assertTrue(blocked.terminal) + self.assertEqual(blocked.next_action, "repair_budget_exhausted") + self.assertEqual(agent.prompts, []) + def test_local_repository_validation_iterates_before_creating_the_implementation_pr(self) -> None: agent = SequencedFakeAgent([ AgentResult( From 98c7c5dca5e2b7816325084c346d06df45ee4f7f Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 15:58:26 +0200 Subject: [PATCH 58/87] fix: make EP builds read committed version --- .github/workflows/canonical-versioning.yml | 54 +-------- .../engineering-platform-validation.yml | 5 +- .../test_platform_productization.py | 22 ++-- tools/qualification/advance_platform_build.py | 114 +++++++++++++++--- tools/qualification/build_platform_wheel.py | 10 +- .../platform_version_consistency.py | 5 + 6 files changed, 124 insertions(+), 86 deletions(-) diff --git a/.github/workflows/canonical-versioning.yml b/.github/workflows/canonical-versioning.yml index 3a04daa3..aa985e1b 100644 --- a/.github/workflows/canonical-versioning.yml +++ b/.github/workflows/canonical-versioning.yml @@ -5,61 +5,17 @@ on: branches: - '**' -# One writer per ref prevents two near-simultaneous pushes from creating two -# version commits on the same branch. -concurrency: - group: canonical-version-${{ github.ref }} - cancel-in-progress: false - permissions: - contents: write + contents: read jobs: - feature-patch: - name: Add the first feature-branch patch version - if: github.ref_name != 'main' && !startsWith(github.ref_name, 'release-') && github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 - with: - fetch-depth: 0 - - name: Detect an existing canonical version commit - id: existing - run: | - set -euo pipefail - git fetch --no-tags origin main - if git log --format=%s origin/main..HEAD | grep -Eq '^build: advance canonical EP patch version [0-9]+\.[0-9]+\.[0-9]+$'; then - echo 'present=true' >> "$GITHUB_OUTPUT" - else - echo 'present=false' >> "$GITHUB_OUTPUT" - fi - - name: Advance patch version once for this feature branch - if: steps.existing.outputs.present != 'true' - run: | - set -euo pipefail - version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump patch | sed -n 's/^EP_BUILD_VERSION=//p')" - python3 -m pip install --quiet . - python3 tools/qualification/platform_version_consistency.py --source-root . - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add pyproject.toml package.json package-lock.json src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json src/engineering_platform/templates/workspace-config.json src/engineering_platform/platform_version.py - git commit -m "build: advance canonical EP patch version ${version}" - git push origin "HEAD:${GITHUB_REF_NAME}" - - main-minor: - name: Advance the canonical minor version on main - if: github.ref_name == 'main' && github.actor != 'github-actions[bot]' + validate-version-source: + name: Validate committed canonical version source runs-on: ubuntu-latest steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 - - name: Advance minor version and commit it to main + - name: Read-only source consistency run: | - set -euo pipefail - version="$(python3 tools/qualification/advance_platform_build.py --source-root . --bump minor | sed -n 's/^EP_BUILD_VERSION=//p')" python3 -m pip install --quiet . + python3 tools/qualification/advance_platform_build.py --source-root . --check python3 tools/qualification/platform_version_consistency.py --source-root . - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add pyproject.toml package.json package-lock.json src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json src/engineering_platform/templates/workspace-config.json src/engineering_platform/platform_version.py - git commit -m "build: advance canonical EP minor version ${version}" - git push origin HEAD:main diff --git a/.github/workflows/engineering-platform-validation.yml b/.github/workflows/engineering-platform-validation.yml index 16bb4e44..eb65ee82 100644 --- a/.github/workflows/engineering-platform-validation.yml +++ b/.github/workflows/engineering-platform-validation.yml @@ -63,10 +63,11 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7 with: {python-version: '3.12'} - - name: Build and install next canonical Engineering Platform wheel + - name: Build and install committed canonical Engineering Platform wheel run: | python3 -m pip install build coverage - python3 tools/qualification/build_platform_wheel.py --source-root . --install-python "$(command -v python3)" + version="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')" + python3 tools/qualification/build_platform_wheel.py --source-root . --version "$version" --install-python "$(command -v python3)" - name: Import, bytecode and qualification smoke run: | python3 -c "import engineering_platform, engineering_platform.execution_host" diff --git a/tests/engineering/test_platform_productization.py b/tests/engineering/test_platform_productization.py index 53fa1789..b9ceba62 100644 --- a/tests/engineering/test_platform_productization.py +++ b/tests/engineering/test_platform_productization.py @@ -4,6 +4,7 @@ from pathlib import Path import json import sqlite3 +import shutil import tempfile import tomllib import unittest @@ -156,12 +157,11 @@ def test_canonical_wheel_build_advances_one_patch_across_all_projections(self) - for relative in _version_projection_files(): target = root / relative target.parent.mkdir(parents=True, exist_ok=True) - target.write_text('version = "2.1.0"\n', encoding="utf-8") + shutil.copy2(ROOT / relative, target) + module.set_version(root, "2.1.0") self.assertEqual(module.advance(root), "2.1.1") self.assertEqual(module.advance(root), "2.1.2") - for path in root.rglob("*"): - if path.is_file(): - self.assertIn("2.1.2", path.read_text(encoding="utf-8")) + self.assertEqual(module._current_version(root), "2.1.2") def test_canonical_versioning_can_advance_a_minor_and_resets_patch(self) -> None: import importlib.util @@ -175,11 +175,10 @@ def test_canonical_versioning_can_advance_a_minor_and_resets_patch(self) -> None for relative in _version_projection_files(): target = root / relative target.parent.mkdir(parents=True, exist_ok=True) - target.write_text('version = "2.1.6"\n', encoding="utf-8") + shutil.copy2(ROOT / relative, target) + module.set_version(root, "2.1.6") self.assertEqual(module.advance(root, component="minor"), "2.2.0") - for path in root.rglob("*"): - if path.is_file(): - self.assertIn("2.2.0", path.read_text(encoding="utf-8")) + self.assertEqual(module._current_version(root), "2.2.0") def test_release_build_can_set_an_exact_branch_version_across_all_projections(self) -> None: import importlib.util @@ -193,11 +192,10 @@ def test_release_build_can_set_an_exact_branch_version_across_all_projections(se for relative in _version_projection_files(): target = root / relative target.parent.mkdir(parents=True, exist_ok=True) - target.write_text('version = "2.1.6"\n', encoding="utf-8") + shutil.copy2(ROOT / relative, target) + module.set_version(root, "2.1.6") self.assertEqual(module.set_version(root, "2.2.0"), "2.2.0") - for path in root.rglob("*"): - if path.is_file(): - self.assertIn("2.2.0", path.read_text(encoding="utf-8")) + self.assertEqual(module._current_version(root), "2.2.0") with self.assertRaisesRegex(RuntimeError, "stable X.Y.Z"): module.set_version(root, "2.2") diff --git a/tools/qualification/advance_platform_build.py b/tools/qualification/advance_platform_build.py index 04e19661..b7e23072 100644 --- a/tools/qualification/advance_platform_build.py +++ b/tools/qualification/advance_platform_build.py @@ -1,16 +1,21 @@ #!/usr/bin/env python3 -"""Update the canonical Engineering Platform version for a wheel build. +"""Plan or apply an explicit, field-aware EP version operation. -The version is deliberately a checked-in, cross-surface release fact. A build -therefore advances it once before packaging rather than letting individual -Server, Console, Runner, or wheel metadata drift independently. +Normal builds only read the version already committed in ``pyproject.toml``. +This helper is the separate source mutation boundary; it has no publication or +qualification authority and its per-file atomic replacements are not claimed to +be a multi-file transaction. """ from __future__ import annotations import argparse +import json +import os from pathlib import Path import re +import tempfile +import tomllib _VERSION = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$") @@ -25,30 +30,93 @@ ) -def _replace(path: Path, before: str, after: str) -> None: - text = path.read_text(encoding="utf-8") - if before not in text: - raise RuntimeError(f"canonical version {before} is absent from {path}") - path.write_text(text.replace(before, after), encoding="utf-8") +def _atomic_write(path: Path, text: str) -> None: + mode = path.stat().st_mode + fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent) + try: + with os.fdopen(fd, "w", encoding="utf-8") as handle: + handle.write(text) + handle.flush() + os.fsync(handle.fileno()) + os.chmod(temporary, mode) + os.replace(temporary, path) + except BaseException: + try: + os.unlink(temporary) + except FileNotFoundError: + pass + raise def _current_version(root: Path) -> str: root = root.resolve() - pyproject = root / "pyproject.toml" - match = re.search(r'^version = "([^"]+)"$', pyproject.read_text(encoding="utf-8"), re.MULTILINE) - if match is None or _VERSION.fullmatch(match.group(1)) is None: + try: + value = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"] + except (OSError, KeyError, TypeError, tomllib.TOMLDecodeError) as error: + raise RuntimeError("the canonical package version is unreadable") from error + if not isinstance(value, str) or _VERSION.fullmatch(value) is None: raise RuntimeError("the canonical package version must be a stable X.Y.Z release") - return match.group(1) + return value + + +def _json_projection(path: Path, current: str, target: str, field: tuple[str, ...]) -> str: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + item: object = payload + for key in field[:-1]: + if not isinstance(item, dict): raise RuntimeError(f"invalid object projection {path}") + item = item[key] + if not isinstance(item, dict) or item.get(field[-1]) != current: + raise RuntimeError(f"canonical version projection drift in {path}") + item[field[-1]] = target + return json.dumps(payload, indent=2) + "\n" + except (OSError, KeyError, TypeError, json.JSONDecodeError) as error: + raise RuntimeError(f"invalid canonical version projection {path}") from error + + +def _python_projection(path: Path, current: str, target: str) -> str: + text = path.read_text(encoding="utf-8") + old = f'CURRENT_PLATFORM_VERSION = "{current}"' + if text.count(old) != 1: + raise RuntimeError(f"canonical runtime version projection drift in {path}") + return text.replace(old, f'CURRENT_PLATFORM_VERSION = "{target}"', 1) -def set_version(root: Path, version: str) -> str: +def _toml_projection(path: Path, current: str, target: str) -> str: + text = path.read_text(encoding="utf-8") + old = f'version = "{current}"' + if text.count(old) != 1: + raise RuntimeError(f"canonical package version projection drift in {path}") + return text.replace(old, f'version = "{target}"', 1) + + +def _prepared_writes(root: Path, current: str, target: str) -> dict[Path, str]: + paths = {relative: root / relative for relative in VERSION_PROJECTION_PATHS} + # Read and validate every projection before touching any source file. + return { + paths["pyproject.toml"]: _toml_projection(paths["pyproject.toml"], current, target), + paths["package.json"]: _json_projection(paths["package.json"], current, target, ("version",)), + paths["package-lock.json"]: _json_projection(paths["package-lock.json"], current, target, ("version",)), + paths["src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json"]: _json_projection(paths["src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json"], current, target, ("platform_version",)), + paths["src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json"]: _json_projection(paths["src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json"], current, target, ("platform", "version")), + paths["src/engineering_platform/templates/workspace-config.json"]: _json_projection(paths["src/engineering_platform/templates/workspace-config.json"], current, target, ("platform", "version")), + paths["src/engineering_platform/platform_version.py"]: _python_projection(paths["src/engineering_platform/platform_version.py"], current, target), + } + + +def set_version(root: Path, version: str, *, expected_version: str | None = None) -> str: """Set every public EP version projection to one stable release version.""" root = root.resolve() if _VERSION.fullmatch(version) is None: raise RuntimeError("the requested release version must be a stable X.Y.Z release") current = _current_version(root) - for relative_path in VERSION_PROJECTION_PATHS: - _replace(root / relative_path, current, version) + if expected_version is not None and expected_version != current: + raise RuntimeError(f"stale version operation: expected {expected_version}, found {current}") + writes = _prepared_writes(root, current, version) + if version == current: + return version + for path, text in writes.items(): + _atomic_write(path, text) return version @@ -66,12 +134,20 @@ def advance(root: Path, *, component: str = "patch") -> str: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Advance one canonical EP wheel build number") + parser = argparse.ArgumentParser(description="Apply an explicit canonical EP version operation") parser.add_argument("--source-root", type=Path, default=Path.cwd()) parser.add_argument("--set-version", help="set all canonical projections to this exact stable X.Y.Z version") - parser.add_argument("--bump", choices=("patch", "minor"), default="patch", help="semantic-version component to advance when --set-version is absent") + parser.add_argument("--bump", choices=("patch", "minor"), help="semantic-version component to advance") + parser.add_argument("--expected-version") + parser.add_argument("--check", action="store_true") args = parser.parse_args(argv) - version = set_version(args.source_root, args.set_version) if args.set_version else advance(args.source_root, component=args.bump) + if args.check: + if args.bump or args.set_version: parser.error("--check cannot change a version") + version = _current_version(args.source_root) + elif (args.bump is None) == (args.set_version is None): + parser.error("provide exactly one of --bump or --set-version") + else: + version = set_version(args.source_root, args.set_version, expected_version=args.expected_version) if args.set_version else advance(args.source_root, component=args.bump) print(f"EP_BUILD_VERSION={version}") return 0 diff --git a/tools/qualification/build_platform_wheel.py b/tools/qualification/build_platform_wheel.py index e3b0a094..f8ef7b27 100644 --- a/tools/qualification/build_platform_wheel.py +++ b/tools/qualification/build_platform_wheel.py @@ -8,19 +8,21 @@ import subprocess import sys -from advance_platform_build import advance, set_version +from advance_platform_build import _current_version def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Advance, build, and optionally install an EP wheel") + parser = argparse.ArgumentParser(description="Build an already-versioned EP wheel without source mutation") parser.add_argument("--source-root", type=Path, default=Path.cwd()) parser.add_argument("--wheel-directory", type=Path) parser.add_argument("--install-python", type=Path) - parser.add_argument("--version", help="build this exact stable X.Y.Z release instead of advancing a patch") + parser.add_argument("--version", help="require this already-committed stable X.Y.Z release") parser.add_argument("--sdist", action="store_true", help="also create the matching source distribution") args = parser.parse_args(argv) root = args.source_root.resolve() - version = set_version(root, args.version) if args.version else advance(root) + version = _current_version(root) + if args.version is not None and args.version != version: + 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: diff --git a/tools/qualification/platform_version_consistency.py b/tools/qualification/platform_version_consistency.py index 755ee05c..9caf49b0 100644 --- a/tools/qualification/platform_version_consistency.py +++ b/tools/qualification/platform_version_consistency.py @@ -33,11 +33,16 @@ def main(argv: list[str] | None = None) -> int: manifest = json.loads(manifest_path.read_text(encoding="utf-8")) expected = manifest["platform_version"] package = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) + package_json = json.loads((root / "package.json").read_text(encoding="utf-8")) + package_lock = json.loads((root / "package-lock.json").read_text(encoding="utf-8")) configuration = json.loads((source / "engineering_platform" / "ENGINEERING_PLATFORM_CONFIG.json").read_text(encoding="utf-8")) template = json.loads((source / "engineering_platform" / "templates" / "workspace-config.json").read_text(encoding="utf-8")) projections = { "package": package["project"]["version"], + "package_json_root": package_json["version"], + "package_lock_root": package_lock["version"], + "package_lock_workspace_root": package_lock["packages"][""]["version"], "installed_package": installed_version("engineering-platform"), "manifest_platform": manifest["platform_version"], "manifest_runner": manifest["runner_version"], From a04549122b7aeb24570c9905fd959699950ce8bc Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:06:06 +0200 Subject: [PATCH 59/87] fix: make queue decline direct and revisioned --- src/engineering_platform/assets/dashboard.js | 9 ++-- src/engineering_platform/server.py | 25 +++++++-- .../submission_service.py | 53 +++++++++++++++---- tests/engineering/test_submission_service.py | 12 +++++ 4 files changed, 83 insertions(+), 16 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 4f58ad37..390e55ed 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -962,9 +962,12 @@ function queueItems(x, queueDepth) { body.append(title, meta); row.append(number, body); if (defer) actions.append(defer); - if (item.queue_source === "CENTRAL" && item.queue_state === "QUEUED") { - [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."], - ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]].forEach(([disposition, actionKey, reason]) => { + if (item.queue_source === "CENTRAL" && ["QUEUED", "QUARANTINED"].includes(item.queue_state)) { + (item.queue_state === "QUEUED" + ? [["QUARANTINED", "queue.quarantine", "Operator quarantined this submission from Operations Console."], + ["DECLINED", "queue.decline", "Operator declined this submission from Operations Console."]] + : [["DECLINED", "queue.decline", "Operator declined this quarantined submission from Operations Console."]] + ).forEach(([disposition, actionKey, reason]) => { const action = document.createElement("button"); action.className = `queue-defer${disposition === "DECLINED" ? " queue-defer--destructive" : ""}`; action.type = "button"; diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index c28aacaa..48a83158 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -92,7 +92,7 @@ # bootstrap is deliberately separate from the retired predecessor migration # machinery: it creates a clean installation only and never accepts a source # database path. -SERVER_STORE_SCHEMA_VERSION = 54 +SERVER_STORE_SCHEMA_VERSION = 55 SERVER_ENVIRONMENT_DATA_ROOT = "EP_SERVER_DATA_ROOT" FILE_INBOX_DIRECTORY = "file-inbox" HTTP_JSON_OPENAPI_PATH = "/v1/openapi.json" @@ -515,6 +515,7 @@ class ServerConfigurationError(ValueError): "ep_submissions", "ep_submission_events", "ep_submission_prompt_history", + "ep_queue_disposition_operations", "ep_parity_lifecycle_dispatches", "ep_receipt_run_provenance", "ep_external_producer_bindings", @@ -1199,6 +1200,19 @@ def _migrate_schema_54(connection: sqlite3.Connection) -> None: connection.execute("UPDATE ep_installations SET schema_version=54") +def _migrate_schema_55(connection: sqlite3.Connection) -> None: + """Add durable CAS and idempotency evidence for CENTRAL queue commands.""" + connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema54") + connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52,53,54,55)))") + connection.execute("INSERT INTO ep_installations SELECT instance_id,created_at,55 FROM ep_installations_schema54") + connection.execute("DROP TABLE ep_installations_schema54") + connection.execute("ALTER TABLE ep_submissions ADD COLUMN disposition_revision INTEGER NOT NULL DEFAULT 0") + connection.execute("CREATE TABLE ep_queue_disposition_operations (operation_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), actor_reference TEXT NOT NULL, command_digest TEXT NOT NULL, from_state TEXT NOT NULL, to_state TEXT NOT NULL, previous_revision INTEGER NOT NULL, resulting_revision INTEGER NOT NULL, event_id INTEGER NOT NULL REFERENCES ep_submission_events(event_id), recorded_at TEXT NOT NULL)") + connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(55)") + connection.execute("UPDATE engineering_metadata SET value='55' WHERE key='installation.schema_version'") + connection.execute("UPDATE ep_installations SET schema_version=55") + + def validate_store(data_root: Path, identity: RuntimeIdentity) -> dict[str, object]: """Return a deterministic fail-closed current-schema structural report.""" path = data_root / SERVER_DATABASE_FILENAME @@ -1264,14 +1278,14 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int existing_tables = _table_names(existing) if existing_tables: current_schema = _schema_version(existing) - if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, SERVER_STORE_SCHEMA_VERSION}: + if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, SERVER_STORE_SCHEMA_VERSION}: raise ServerConfigurationError( f"EP Server store is not a valid official schema-{SERVER_STORE_SCHEMA_VERSION} installation." ) if current_schema == SERVER_STORE_SCHEMA_VERSION: validate_store(data_root, identity) return identity - if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53}: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54}: with sqlite3.connect(database_path) as connection: # Schema-49 rebuilds the submission parent table # to widen its immutable transport constraint. @@ -1300,7 +1314,9 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_52(connection) if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}: _migrate_schema_53(connection) - _migrate_schema_54(connection) + if current_schema != 54: + _migrate_schema_54(connection) + _migrate_schema_55(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") validate_store(data_root, identity) @@ -1328,6 +1344,7 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_52(connection) _migrate_schema_53(connection) _migrate_schema_54(connection) + _migrate_schema_55(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") connection.execute("PRAGMA foreign_keys=ON") diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index 340f7077..79130abf 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -11,6 +11,7 @@ from datetime import datetime, timezone import hashlib import json +import unicodedata import secrets import sqlite3 from pathlib import Path @@ -48,28 +49,62 @@ def __init__(self, code: str, status: int = 400) -> None: def operator_queue_disposition(connection: sqlite3.Connection, *, project_id: str, - submission_id: str, disposition: str, reason: str) -> dict[str, str]: + submission_id: str, disposition: str, reason: str, + expected_state: str | None = None, + expected_revision: int | None = None, + operation_id: str | None = None, + actor_reference: str = "LEGACY_NOT_RECORDED") -> dict[str, object]: """Apply one auditable CENTRAL queue disposition; never delete intent.""" - if disposition not in OPERATOR_QUEUE_STATES | {"QUEUED"} or not reason.strip() or len(reason) > 500: + if (not isinstance(submission_id, str) or not isinstance(disposition, str) + or not isinstance(reason, str) or not isinstance(actor_reference, str)): raise SubmissionError("INVALID_QUEUE_DISPOSITION") + normalized_reason = unicodedata.normalize("NFC", reason).strip() + if (disposition not in OPERATOR_QUEUE_STATES | {"QUEUED"} or not normalized_reason + or len(normalized_reason) > 500 or any(ord(char) < 32 and char not in "\t" for char in normalized_reason)): + raise SubmissionError("INVALID_QUEUE_DISPOSITION") + command_digest = hashlib.sha256(json.dumps( + [project_id, submission_id, expected_state, expected_revision, disposition, normalized_reason], + separators=(",", ":"), + ).encode()).hexdigest() + if operation_id is not None: + if not isinstance(operation_id, str) or not operation_id or len(operation_id) > 128: + raise SubmissionError("INVALID_QUEUE_DISPOSITION") + prior = connection.execute( + "SELECT command_digest,to_state,resulting_revision,recorded_at FROM ep_queue_disposition_operations WHERE operation_id=?", + (operation_id,), + ).fetchone() + if prior is not None: + if str(prior[0]) != command_digest: + raise SubmissionError("OPERATION_ID_CONFLICT", 409) + return {"submission_id": submission_id, "state": str(prior[1]), "reason": normalized_reason, + "recorded_at": str(prior[3]), "resulting_revision": int(prior[2]), "operation_id": operation_id, + "replayed": True} row = connection.execute( - "SELECT state FROM ep_submissions WHERE project_id=? AND submission_id=?", (project_id, submission_id) + "SELECT state,admission,disposition_revision FROM ep_submissions WHERE project_id=? AND submission_id=?", (project_id, submission_id) ).fetchone() if row is None: raise SubmissionError("SUBMISSION_NOT_FOUND", 404) - current = str(row[0]) + current, admission, revision = str(row[0]), str(row[1]), int(row[2]) + if admission != "ADMITTED" or (expected_state is not None and expected_state != current) or (expected_revision is not None and expected_revision != revision): + raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) + if connection.execute("SELECT 1 FROM ep_parity_lifecycle_dispatches WHERE submission_id=?", (submission_id,)).fetchone() is not None: + raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) allowed = (current == "QUEUED" and disposition in OPERATOR_QUEUE_STATES) or ( current in {"DEFERRED", "QUARANTINED"} and disposition == "QUEUED" - ) + ) or (current == "QUARANTINED" and disposition == "DECLINED") if not allowed: raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) now = _now() - connection.execute("UPDATE ep_submissions SET state=? WHERE project_id=? AND submission_id=?", (disposition, project_id, submission_id)) - connection.execute( + changed = connection.execute("UPDATE ep_submissions SET state=?,disposition_revision=disposition_revision+1 WHERE project_id=? AND submission_id=? AND state=? AND disposition_revision=?", (disposition, project_id, submission_id, current, revision)).rowcount + if changed != 1: + raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) + event = connection.execute( "INSERT INTO ep_submission_events(submission_id,event_kind,payload,recorded_at) VALUES(?,?,?,?)", - (submission_id, "OPERATOR_QUEUE_" + disposition, json.dumps({"state": disposition, "reason": reason.strip()}, sort_keys=True), now), + (submission_id, "OPERATOR_QUEUE_" + disposition, json.dumps({"state": disposition, "reason": normalized_reason, "actor_reference": actor_reference, "from_state": current, "previous_revision": revision, "resulting_revision": revision + 1, "operation_id": operation_id}, sort_keys=True), now), ) - return {"submission_id": submission_id, "state": disposition, "reason": reason.strip(), "recorded_at": now} + if operation_id is not None: + connection.execute("INSERT INTO ep_queue_disposition_operations(operation_id,project_id,submission_id,actor_reference,command_digest,from_state,to_state,previous_revision,resulting_revision,event_id,recorded_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)", (operation_id, project_id, submission_id, actor_reference, command_digest, current, disposition, revision, revision + 1, event.lastrowid, now)) + return {"submission_id": submission_id, "state": disposition, "reason": normalized_reason, "recorded_at": now, "previous_revision": revision, "resulting_revision": revision + 1, "operation_id": operation_id} def _lifecycle_payload(*, transport: str, producer_id: str) -> dict[str, str]: diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index bcdf27e7..71ee1270 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -59,6 +59,18 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s submission_service.producer_readback(connection, project_id="djconnect", submission_id=submitted.submission_id)["submission"]["state"], "QUARANTINED", ) + self.assertEqual(submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="DECLINED", reason="The operator rejected the quarantined submission", + )["state"], "DECLINED") + events = [row[0] for row in connection.execute("SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,))] + self.assertEqual(events[-2:], ["OPERATOR_QUEUE_QUARANTINED", "OPERATOR_QUEUE_DECLINED"]) + self.assertNotIn("OPERATOR_QUEUE_QUEUED", events[-2:]) + submitted = submission_service.submit(connection, submission_service.request_from_mapping("djconnect", self.payload("resume"), transport="HTTP")) + held = submission_service.operator_queue_disposition( + connection, project_id="djconnect", submission_id=submitted.submission_id, + disposition="QUARANTINED", reason="Needs operator review", + ) resumed = submission_service.operator_queue_disposition( connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUEUED", reason="Review completed", From b7a9cce031080409ccd1fc502a94bacd1d1d5928 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:08:15 +0200 Subject: [PATCH 60/87] fix: hide queue mutations for terminal states --- src/engineering_platform/assets/dashboard.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 390e55ed..5db62930 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -942,11 +942,13 @@ function queueItems(x, queueDepth) { ? locale.dateTime(new Date(modified)) : t("format.timestamp_unavailable"), }); - const defer = document.createElement("button"); + const central = item.queue_source === "CENTRAL"; + const mutable = !central || ["QUEUED", "DEFERRED", "QUARANTINED"].includes(item.queue_state); + const defer = mutable ? document.createElement("button") : null; if (defer) { defer.className = "queue-defer"; defer.type = "button"; - const held = item.queue_source === "CENTRAL" && ["DEFERRED", "QUARANTINED"].includes(item.queue_state); + const held = central && ["DEFERRED", "QUARANTINED"].includes(item.queue_state); const actionKey = held ? "queue.resume" : "queue.defer"; defer.textContent = t(`${actionKey}_action`); defer.title = t(`${actionKey}_action`); From 328f179ebde4ceff19c243d83c2651564d318b7a Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:14:43 +0200 Subject: [PATCH 61/87] feat: secure versioned queue dispositions --- src/engineering_platform/assets/dashboard.js | 4 ++- src/engineering_platform/parity_context.py | 3 +- src/engineering_platform/server.py | 31 +++++++++++++++++-- .../submission_service.py | 19 +++++++++--- tests/engineering/test_submission_service.py | 19 ++++++++---- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/engineering_platform/assets/dashboard.js b/src/engineering_platform/assets/dashboard.js index 5db62930..6af7c5c0 100644 --- a/src/engineering_platform/assets/dashboard.js +++ b/src/engineering_platform/assets/dashboard.js @@ -999,8 +999,10 @@ function queueDisposition(item, disposition, reason, button) { .then((confirmed) => { if (!confirmed) return; button.disabled = true; + const operationId = crypto.randomUUID(); return fetch("/api/queue-disposition", { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ submission_id: submissionId, disposition, reason }) }) + body: JSON.stringify({ contract_version: "1.0", operation_id: operationId, submission_id: submissionId, + expected_state: item.queue_state, expected_revision: item.disposition_revision, disposition, reason }) }) .then(async (response) => ({ ok: response.ok, body: await response.json().catch(() => ({})) })) .then((result) => { if (!result.ok) throw Error(result.body.error || t("queue.defer_failed")); return refreshDashboard(); }) .catch((error) => showDashboardError(error.message, t("queue.defer_failed"))) diff --git a/src/engineering_platform/parity_context.py b/src/engineering_platform/parity_context.py index 7df67e08..2754c1ae 100644 --- a/src/engineering_platform/parity_context.py +++ b/src/engineering_platform/parity_context.py @@ -90,7 +90,7 @@ def console_queue_projection(self, *, limit: int = 25) -> dict[str, object]: it; terminal records remain durable history rather than queue items. """ rows = self.connection.execute( - """SELECT s.submission_id,s.transport,s.producer_type,s.created_at,s.state + """SELECT s.submission_id,s.transport,s.producer_type,s.created_at,s.state,s.disposition_revision FROM ep_submissions s LEFT JOIN ep_parity_lifecycle_dispatches d ON d.submission_id=s.submission_id WHERE s.project_id=? AND s.state IN ('QUEUED','DEFERRED','QUARANTINED') AND s.admission='ADMITTED' @@ -109,6 +109,7 @@ def console_queue_projection(self, *, limit: int = 25) -> dict[str, object]: "queue_source": "CENTRAL", "transport": str(row[1]), "queue_state": str(row[4]), + "disposition_revision": int(row[5]), } for row in rows[:limit] ] diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 48a83158..6cc41f4b 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -516,6 +516,7 @@ class ServerConfigurationError(ValueError): "ep_submission_events", "ep_submission_prompt_history", "ep_queue_disposition_operations", + "ep_operator_capabilities", "ep_parity_lifecycle_dispatches", "ep_receipt_run_provenance", "ep_external_producer_bindings", @@ -1208,6 +1209,7 @@ def _migrate_schema_55(connection: sqlite3.Connection) -> None: connection.execute("DROP TABLE ep_installations_schema54") connection.execute("ALTER TABLE ep_submissions ADD COLUMN disposition_revision INTEGER NOT NULL DEFAULT 0") connection.execute("CREATE TABLE ep_queue_disposition_operations (operation_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), actor_reference TEXT NOT NULL, command_digest TEXT NOT NULL, from_state TEXT NOT NULL, to_state TEXT NOT NULL, previous_revision INTEGER NOT NULL, resulting_revision INTEGER NOT NULL, event_id INTEGER NOT NULL REFERENCES ep_submission_events(event_id), recorded_at TEXT NOT NULL)") + connection.execute("CREATE TABLE ep_operator_capabilities (consumer_id TEXT NOT NULL, project_id TEXT NOT NULL, capability TEXT NOT NULL CHECK(capability IN ('QUEUE_HOLD_RESUME','QUEUE_DECLINE')), granted_at TEXT NOT NULL, revoked_at TEXT, PRIMARY KEY(consumer_id,project_id,capability))") connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(55)") connection.execute("UPDATE engineering_metadata SET value='55' WHERE key='installation.schema_version'") connection.execute("UPDATE ep_installations SET schema_version=55") @@ -2565,6 +2567,15 @@ def _authenticated_consumer(connection: sqlite3.Connection, token: object, proje return str(row[0]) if row else None +def _operator_capability(connection: sqlite3.Connection, token: object, project_id: str, capability: str) -> str | None: + """Resolve an explicit project capability; admission credentials are insufficient.""" + actor = _authenticated_consumer(connection, token, project_id) + if actor is None: + return None + grant = connection.execute("SELECT 1 FROM ep_operator_capabilities WHERE consumer_id=? AND project_id=? AND capability=? AND revoked_at IS NULL", (actor, project_id, capability)).fetchone() + return actor if grant is not None else "" + + def _admit_server_owned_file_inbox( data_root: Path, envelope: dict[str, object], receipt_id: str, received_at: str, ) -> dict[str, object]: @@ -3333,12 +3344,26 @@ def _delegate_dashboard(self, method: str) -> None: if not 2 <= length <= 1024: raise ValueError payload = json.loads(self.rfile.read(length).decode("utf-8")) - if not isinstance(payload, dict) or set(payload) != {"submission_id", "disposition", "reason"}: + expected = {"contract_version", "operation_id", "submission_id", "expected_state", "expected_revision", "disposition", "reason"} + if (not isinstance(payload, dict) or set(payload) != expected + or payload.get("contract_version") != "1.0" + or not all(isinstance(payload.get(field), str) for field in ("operation_id", "submission_id", "expected_state", "disposition", "reason")) + or not isinstance(payload.get("expected_revision"), int) or isinstance(payload.get("expected_revision"), bool)): raise ValueError with sqlite3.connect(self.server.data_root / SERVER_DATABASE_FILENAME) as connection: # type: ignore[attr-defined] + connection.execute("BEGIN IMMEDIATE") + token = self.headers.get("Authorization", "")[7:] if self.headers.get("Authorization", "").startswith("Bearer ") else None + capability = "QUEUE_DECLINE" if payload["disposition"] == "DECLINED" else "QUEUE_HOLD_RESUME" + actor = _operator_capability(connection, token, selected, capability) + if actor is None: + raise submission_service.SubmissionError("UNAUTHENTICATED", 401) + if not actor: + raise submission_service.SubmissionError("OPERATOR_CAPABILITY_REQUIRED", 403) result = submission_service.operator_queue_disposition( - connection, project_id=selected, submission_id=str(payload["submission_id"]), - disposition=str(payload["disposition"]), reason=str(payload["reason"]), + connection, project_id=selected, submission_id=payload["submission_id"], + disposition=payload["disposition"], reason=payload["reason"], + expected_state=payload["expected_state"], expected_revision=payload["expected_revision"], + operation_id=payload["operation_id"], actor_reference=actor, ) self._send(200, result) except submission_service.SubmissionError as error: diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index 79130abf..cc104ef1 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -395,7 +395,7 @@ def issue_consumer_credential(connection: sqlite3.Connection, *, consumer_id: st return {"credential_id": credential_id, "consumer_id": consumer_id, "project_id": project_id, "credential": token} -PRODUCER_READBACK_CONTRACT_VERSION = "1.1" +PRODUCER_READBACK_CONTRACT_VERSION = "1.2" TERMINAL_EVIDENCE_CONTRACT_VERSION = "1.2" _TERMINAL_OUTCOMES = frozenset({"COMPLETE", "BLOCKED", "FAILED"}) @@ -631,7 +631,7 @@ def producer_readback( row = connection.execute( """SELECT s.repository_id,s.producer_id,s.producer_type,s.producer_version, s.prompt_digest,s.constraints,s.correlation_id,s.mission_id, - s.engineering_action_id,s.state,s.admission,s.transport,s.created_at, + s.engineering_action_id,s.state,s.admission,s.transport,s.created_at,s.disposition_revision, d.run_id,d.state,d.operator_resolution,d.updated_at FROM ep_submissions AS s LEFT JOIN ep_parity_lifecycle_dispatches AS d @@ -644,9 +644,20 @@ def producer_readback( ( repository_id, producer_id, producer_type, producer_version, prompt_digest, raw_constraints, correlation_id, mission_id, engineering_action_id, submission_state, - admission, transport, created_at, run_id, dispatch_state, + admission, transport, created_at, disposition_revision, run_id, dispatch_state, operator_resolution, updated_at, ) = row + disposition_row = connection.execute( + "SELECT o.operation_id,o.event_id,o.actor_reference,e.payload,o.recorded_at FROM ep_queue_disposition_operations o JOIN ep_submission_events e ON e.event_id=o.event_id WHERE o.project_id=? AND o.submission_id=? ORDER BY o.recorded_at DESC LIMIT 1", + (project_id, submission_id), + ).fetchone() + disposition = {"state": str(submission_state), "terminal": str(submission_state) == "DECLINED", "execution_eligible": str(submission_state) == "QUEUED", "revision": int(disposition_revision), "operation_id": None, "event_reference": None, "reason": "NOT_RECORDED", "actor_reference": "NOT_RECORDED", "recorded_at": None} + if disposition_row is not None: + try: + reason = json.loads(str(disposition_row[3])).get("reason", "NOT_RECORDED") + except json.JSONDecodeError: + reason = "NOT_RECORDED" + disposition.update({"operation_id": str(disposition_row[0]), "event_reference": "event:" + str(disposition_row[1]), "actor_reference": str(disposition_row[2]), "reason": reason, "recorded_at": str(disposition_row[4])}) constraints = _read_constraints(raw_constraints) if constraints is None: # Existing data is retained, but cannot be represented as qualified @@ -748,7 +759,7 @@ def producer_readback( "engineering_action_id": engineering_action_id, }, "provenance": {"status": provenance_status, "forge_execution": constraints.get("forge_execution")}, - "run": run, "result": result, "evidence": evidence, + "disposition": disposition, "run": run, "result": result, "evidence": evidence, } diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 71ee1270..864ee4ea 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -26,6 +26,8 @@ def setUp(self) -> None: connection.execute("INSERT INTO ep_project_registrations VALUES(?,?,?,?,?)", ("djconnect", "{}", "ACTIVE", now, now)) connection.execute("INSERT INTO ep_repository_registrations VALUES(?,?,?,?,?,?,?)", ("djconnect", "djconnect", "djconnect", "authority", "{}", now, now)) self.credential = submission_service.issue_consumer_credential(connection, consumer_id="cli", project_id="djconnect")["credential"] + connection.execute("INSERT INTO ep_operator_capabilities VALUES(?,?,?,?,?)", ("cli", "djconnect", "QUEUE_HOLD_RESUME", now, None)) + connection.execute("INSERT INTO ep_operator_capabilities VALUES(?,?,?,?,?)", ("cli", "djconnect", "QUEUE_DECLINE", now, None)) def tearDown(self) -> None: server.stop(self.root) @@ -118,13 +120,18 @@ def test_console_queue_actions_change_only_the_selected_submission_and_are_audit server.start(self.root) endpoint = f"http://127.0.0.1:{self.port}/api/queue-disposition?project=djconnect" + current_state, current_revision = "QUEUED", 0 def action(disposition: str, reason: str) -> dict[str, object]: - body = json.dumps({"submission_id": submitted.submission_id, "disposition": disposition, "reason": reason}).encode() + nonlocal current_state, current_revision + revision, expected_state = current_revision, current_state + body = json.dumps({"contract_version": "1.0", "operation_id": f"operation-{disposition}-{revision}", "submission_id": submitted.submission_id, "expected_state": expected_state, "expected_revision": revision, "disposition": disposition, "reason": reason}).encode() request = Request(endpoint, data=body, method="POST", headers={ - "Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}", + "Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}", "Authorization": f"Bearer {self.credential}", }) with urlopen(request) as response: # nosec B310 - return json.loads(response.read()) + result = json.loads(response.read()) + current_state, current_revision = result["state"], result["resulting_revision"] + return result self.assertEqual(action("DEFERRED", "Wait for the maintenance window")["state"], "DEFERRED") self.assertEqual(action("QUEUED", "Maintenance window is open")["state"], "QUEUED") @@ -132,9 +139,9 @@ def action(disposition: str, reason: str) -> dict[str, object]: self.assertEqual(action("QUARANTINED", "Investigate the source envelope")["state"], "QUARANTINED") self.assertEqual(action("QUEUED", "Investigation completed")["state"], "QUEUED") self.assertEqual(action("DECLINED", "The request is no longer needed")["state"], "DECLINED") - body = json.dumps({"submission_id": submitted.submission_id, "disposition": "QUEUED", "reason": "Must not revive a declined request"}).encode() + body = json.dumps({"contract_version": "1.0", "operation_id": "must-not-revive", "submission_id": submitted.submission_id, "expected_state": "DECLINED", "expected_revision": current_revision, "disposition": "QUEUED", "reason": "Must not revive a declined request"}).encode() with self.assertRaises(HTTPError) as rejected: - urlopen(Request(endpoint, data=body, method="POST", headers={"Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}"})) # nosec B310 + urlopen(Request(endpoint, data=body, method="POST", headers={"Content-Type": "application/json", "Origin": f"http://127.0.0.1:{self.port}", "Authorization": f"Bearer {self.credential}"})) # nosec B310 self.assertEqual(rejected.exception.code, 409) with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: events = [row[0] for row in connection.execute( @@ -188,7 +195,7 @@ def test_authenticated_producer_readback_is_exactly_correlated_and_terminal_evid endpoint = f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions/{submission_id}" with urlopen(Request(endpoint, headers={"Authorization": f"Bearer {self.credential}"})) as response: # nosec B310 initial = json.loads(response.read()) - self.assertEqual(initial["contract_version"], "1.1") + self.assertEqual(initial["contract_version"], "1.2") self.assertEqual(initial["correlation"], { "correlation_id": "forge-correlation-1", "mission_id": "mission-1", "engineering_action_id": "action-1", }) From 11b55fe587186dc4add1826814090a3e05e6244b Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:15:54 +0200 Subject: [PATCH 62/87] docs: describe queue disposition readback --- .../EP_PRODUCER_READBACK_CONTRACT.md | 13 ++++++-- .../EXECUTION_HOST_ARCHITECTURE.md | 5 ++-- .../producer-readback-v1.2.schema.json | 30 +++++++++++++++++++ src/engineering_platform/server.py | 2 +- 4 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 src/engineering_platform/schemas/producer-readback-v1.2.schema.json diff --git a/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md b/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md index 04866ed4..25379ac7 100644 --- a/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md +++ b/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md @@ -1,6 +1,6 @@ -# EP producer readback contract v1.1 +# EP producer readback contract v1.2 -`v1.1` is the consumer-visible, authenticated readback contract for a +`v1.2` is the consumer-visible, authenticated readback contract for a canonical EP submission. It belongs to the existing EP Server HTTP JSON API; it is not a second API, consumer database, queue, or execution authority. @@ -20,12 +20,19 @@ storage. ## Response identity and evidence -The JSON response is schema version `1.1` and contains the immutable canonical +The JSON response is schema version `1.2` and contains the immutable canonical submission ID, project/repository IDs, producer provenance, submitted correlation/mission/engineering-action IDs, and a server-computed `accepted_request_digest`. `run` is `null` until CENTRAL has claimed the accepted submission; once present, its run ID and lifecycle state are canonical. +`disposition` is separate from the execution result. It records the current +submission state, monotone revision, worker eligibility and, where an operator +command exists, its operation/event reference, verified actor reference, +reason and timestamp. `DECLINED` is terminal for the submission while keeping +`run: null` and `result.outcome: NOT_STARTED`; EP never fabricates an execution +receipt, commit or terminal artifact for a declined-but-unclaimed submission. + Forge requests place the versioned, exact execution identity under `constraints.forge_execution`: host, repository, correlation, mission and revision, intent and revision, action, runtime-prompt ID/digest, and retry diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index 0aeb97ad..e9673a5c 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -66,6 +66,8 @@ previous lease history. EP serializes mutating work at the repository/execution-scope boundary: no more than one mutating execution may own a scope at once. FIFO is the default queue +ordering within that scope, but admission and selection remain policy-driven; +FIFO is not a second planning authority. ## CENTRAL queue operator handling @@ -84,8 +86,7 @@ Forge observes these dispositions through the canonical producer-readback contract and reconciles its own Action state. EP does not invoke Forge internals, mutate Forge storage, or infer cancellation. Until a versioned Forge callback contract exists, readback is the required reconciliation path. -ordering within that scope, but admission and selection remain policy-driven; -FIFO is not a second planning authority. The active mutation lease starts with +The active mutation lease starts with the accepted execution and is retained through provider work, validation, delivery, finalization and reconciliation. It is released only after terminal or governed recovery evidence establishes that the scope is safe for later diff --git a/src/engineering_platform/schemas/producer-readback-v1.2.schema.json b/src/engineering_platform/schemas/producer-readback-v1.2.schema.json new file mode 100644 index 00000000..25f86a13 --- /dev/null +++ b/src/engineering_platform/schemas/producer-readback-v1.2.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://engineering-platform.local/schemas/producer-readback-v1.2.schema.json", + "title": "Engineering Platform producer readback v1.2 disposition extension", + "type": "object", + "required": ["contract_version", "submission", "disposition", "run", "result", "evidence"], + "properties": { + "contract_version": {"const": "1.2"}, + "submission": {"type": "object", "required": ["state"]}, + "disposition": { + "type": "object", + "additionalProperties": false, + "required": ["state", "terminal", "execution_eligible", "revision", "operation_id", "event_reference", "reason", "actor_reference", "recorded_at"], + "properties": { + "state": {"enum": ["QUEUED", "DEFERRED", "QUARANTINED", "DECLINED"]}, + "terminal": {"type": "boolean"}, + "execution_eligible": {"type": "boolean"}, + "revision": {"type": "integer", "minimum": 0}, + "operation_id": {"type": ["string", "null"]}, + "event_reference": {"type": ["string", "null"]}, + "reason": {"type": "string"}, + "actor_reference": {"type": "string"}, + "recorded_at": {"type": ["string", "null"]} + } + }, + "run": {"type": ["object", "null"]}, + "result": {"type": "object"}, + "evidence": {"type": "object"} + } +} diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 6cc41f4b..3b70216c 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -221,7 +221,7 @@ def _http_json_openapi_document() -> dict[str, object]: "schema": {"type": "string"}}, ], "responses": { - "200": {"description": "Canonical project-scoped producer readback v1.1"}, + "200": {"description": "Canonical project-scoped producer readback v1.2"}, "401": {"description": "Missing or invalid consumer credential"}, "404": {"description": "Submission absent from the authenticated project"}, }, From 0e35c1f7027159bb7aa5fdc41e9e006c49bd7132 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:16:11 +0200 Subject: [PATCH 63/87] test: cover disposition readback without execution --- tests/engineering/test_submission_service.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 864ee4ea..3dea8432 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -57,6 +57,8 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s disposition="QUARANTINED", reason="Needs operator review", ) self.assertEqual(held["state"], "QUARANTINED") + readback = submission_service.producer_readback(connection, project_id="djconnect", submission_id=submitted.submission_id) + self.assertEqual(readback["disposition"], {"state": "QUARANTINED", "terminal": False, "execution_eligible": False, "revision": 1, "operation_id": None, "event_reference": None, "reason": "NOT_RECORDED", "actor_reference": "NOT_RECORDED", "recorded_at": None}) self.assertEqual( submission_service.producer_readback(connection, project_id="djconnect", submission_id=submitted.submission_id)["submission"]["state"], "QUARANTINED", @@ -65,6 +67,10 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="DECLINED", reason="The operator rejected the quarantined submission", )["state"], "DECLINED") + declined_readback = submission_service.producer_readback(connection, project_id="djconnect", submission_id=submitted.submission_id) + self.assertIsNone(declined_readback["run"]) + self.assertEqual(declined_readback["result"]["outcome"], "NOT_STARTED") + self.assertTrue(declined_readback["disposition"]["terminal"]) events = [row[0] for row in connection.execute("SELECT event_kind FROM ep_submission_events WHERE submission_id=? ORDER BY event_id", (submitted.submission_id,))] self.assertEqual(events[-2:], ["OPERATOR_QUEUE_QUARANTINED", "OPERATOR_QUEUE_DECLINED"]) self.assertNotIn("OPERATOR_QUEUE_QUEUED", events[-2:]) From 3f9320dc90b1a05d2db6aa54a3cf58e7b0988280 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:16:41 +0200 Subject: [PATCH 64/87] test: reject queue mutation after lifecycle claim --- tests/engineering/test_submission_service.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 3dea8432..2ef23206 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -106,6 +106,17 @@ def test_operator_dispositions_are_audited_and_only_resumable_from_hold_states(s disposition="QUEUED", reason="Must not revive a decline", ) + def test_claimed_submission_rejects_queue_mutation_without_audit_event(self) -> None: + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit(connection, submission_service.request_from_mapping("djconnect", self.payload("claimed"), transport="HTTP")) + connection.execute("INSERT INTO ep_execution_runs(run_id,project_id,state,created_at,updated_at,execution_mode) VALUES(?,?,?,?,?,?)", ("run-claimed", "djconnect", "CLAIMED", "now", "now", "MANAGED")) + connection.execute("INSERT INTO ep_parity_lifecycle_dispatches(submission_id,project_id,repository_id,run_id,state,prompt_path,claimed_at,updated_at,operator_resolution) VALUES(?,?,?,?,?,?,?,?,?)", (submitted.submission_id, "djconnect", "djconnect", "run-claimed", "CLAIMED", "prompt", "now", "now", "NONE")) + before = connection.execute("SELECT state,disposition_revision FROM ep_submissions WHERE submission_id=?", (submitted.submission_id,)).fetchone() + with self.assertRaisesRegex(submission_service.SubmissionError, "QUEUE_DISPOSITION_CONFLICT"): + submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUARANTINED", reason="Too late") + self.assertEqual(connection.execute("SELECT state,disposition_revision FROM ep_submissions WHERE submission_id=?", (submitted.submission_id,)).fetchone(), before) + self.assertEqual(connection.execute("SELECT COUNT(*) FROM ep_submission_events WHERE submission_id=? AND event_kind LIKE 'OPERATOR_QUEUE_%'", (submitted.submission_id,)).fetchone()[0], 0) + def test_http_auth_scope_and_acceptance(self) -> None: server.start(self.root) request = Request(f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions", data=json.dumps(self.payload("http")).encode(), headers={"Authorization": f"Bearer {self.credential}", "Content-Type": "application/json"}, method="POST") From 266b9ad59c86502756c1a556075a574909e5b849 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:17:08 +0200 Subject: [PATCH 65/87] test: cover idempotent queue operations --- tests/engineering/test_submission_service.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 2ef23206..05ed0f2c 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -117,6 +117,17 @@ def test_claimed_submission_rejects_queue_mutation_without_audit_event(self) -> self.assertEqual(connection.execute("SELECT state,disposition_revision FROM ep_submissions WHERE submission_id=?", (submitted.submission_id,)).fetchone(), before) self.assertEqual(connection.execute("SELECT COUNT(*) FROM ep_submission_events WHERE submission_id=? AND event_kind LIKE 'OPERATOR_QUEUE_%'", (submitted.submission_id,)).fetchone()[0], 0) + def test_queue_operation_id_replays_once_and_rejects_payload_collision(self) -> None: + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit(connection, submission_service.request_from_mapping("djconnect", self.payload("operation"), transport="HTTP")) + first = submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUARANTINED", reason="Investigate source", expected_state="QUEUED", expected_revision=0, operation_id="queue-operation-1", actor_reference="operator-a") + replay = submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUARANTINED", reason="Investigate source", expected_state="QUEUED", expected_revision=0, operation_id="queue-operation-1", actor_reference="operator-a") + self.assertEqual((first["state"], replay["state"]), ("QUARANTINED", "QUARANTINED")) + self.assertTrue(replay["replayed"]) + self.assertEqual(connection.execute("SELECT COUNT(*) FROM ep_submission_events WHERE submission_id=? AND event_kind='OPERATOR_QUEUE_QUARANTINED'", (submitted.submission_id,)).fetchone()[0], 1) + with self.assertRaisesRegex(submission_service.SubmissionError, "OPERATION_ID_CONFLICT"): + submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="DECLINED", reason="Different command", expected_state="QUARANTINED", expected_revision=1, operation_id="queue-operation-1", actor_reference="operator-a") + def test_http_auth_scope_and_acceptance(self) -> None: server.start(self.root) request = Request(f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions", data=json.dumps(self.payload("http")).encode(), headers={"Authorization": f"Bearer {self.credential}", "Content-Type": "application/json"}, method="POST") From 218b98a380a49a212cd9e10421a92d3e417b8a66 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:17:31 +0200 Subject: [PATCH 66/87] test: reject malformed queue disposition reasons --- tests/engineering/test_submission_service.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/engineering/test_submission_service.py b/tests/engineering/test_submission_service.py index 05ed0f2c..d76677f5 100644 --- a/tests/engineering/test_submission_service.py +++ b/tests/engineering/test_submission_service.py @@ -128,6 +128,15 @@ def test_queue_operation_id_replays_once_and_rejects_payload_collision(self) -> with self.assertRaisesRegex(submission_service.SubmissionError, "OPERATION_ID_CONFLICT"): submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="DECLINED", reason="Different command", expected_state="QUARANTINED", expected_revision=1, operation_id="queue-operation-1", actor_reference="operator-a") + def test_queue_disposition_rejects_non_string_or_control_reason_without_mutation(self) -> None: + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + submitted = submission_service.submit(connection, submission_service.request_from_mapping("djconnect", self.payload("invalid-reason"), transport="HTTP")) + for reason in (None, {}, [], True, "\x00bad", "\n"): + with self.assertRaisesRegex(submission_service.SubmissionError, "INVALID_QUEUE_DISPOSITION"): + submission_service.operator_queue_disposition(connection, project_id="djconnect", submission_id=submitted.submission_id, disposition="QUARANTINED", reason=reason) # type: ignore[arg-type] + self.assertEqual(connection.execute("SELECT state,disposition_revision FROM ep_submissions WHERE submission_id=?", (submitted.submission_id,)).fetchone(), ("QUEUED", 0)) + self.assertEqual(connection.execute("SELECT COUNT(*) FROM ep_submission_events WHERE submission_id=? AND event_kind LIKE 'OPERATOR_QUEUE_%'", (submitted.submission_id,)).fetchone()[0], 0) + def test_http_auth_scope_and_acceptance(self) -> None: server.start(self.root) request = Request(f"http://127.0.0.1:{self.port}/v1/projects/djconnect/submissions", data=json.dumps(self.payload("http")).encode(), headers={"Authorization": f"Bearer {self.credential}", "Content-Type": "application/json"}, method="POST") From e4d0bf2c35a0139d7e0dde490b3e9d6c6c557476 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:18:09 +0200 Subject: [PATCH 67/87] docs: define queue disposition state machine --- docs/engineering/EXECUTION_HOST_ARCHITECTURE.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index e9673a5c..bf104838 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -77,15 +77,21 @@ Console operator must never delete a queued submission or silently make it disappear. Queue handling is an explicit, per-submission, reasoned state transition with an append-only audit event and producer-visible readback. -The supported operator dispositions are `DEFERRED`, `QUARANTINED`, and -`DECLINED`; only `DEFERRED` and `QUARANTINED` may be explicitly resumed to -`QUEUED`. A declined submission is terminal but retained with its correlation -and reason. A lifecycle worker selects only admitted `QUEUED` submissions. +The state machine is `QUEUED -> DEFERRED|QUARANTINED|DECLINED`, +`DEFERRED -> QUEUED`, and `QUARANTINED -> QUEUED|DECLINED`. `DECLINED` has no +outgoing transition. Direct `QUARANTINED -> DECLINED` never creates an +intermediate `QUEUED` state or a worker-eligible window. A declined submission +is terminal but retained with its correlation and reason. A lifecycle worker +selects only admitted `QUEUED` submissions; queue disposition never cancels a +claimed run. Forge observes these dispositions through the canonical producer-readback contract and reconciles its own Action state. EP does not invoke Forge internals, mutate Forge storage, or infer cancellation. Until a versioned Forge callback contract exists, readback is the required reconciliation path. +Queue commands carry a versioned operation ID, expected state and monotone +revision, and are arbitrated with the worker claim in CENTRAL. Origin checking +is CSRF protection only, never operator authentication. The active mutation lease starts with the accepted execution and is retained through provider work, validation, delivery, finalization and reconciliation. It is released only after terminal From 64ce1cb598eaae8862bf221dbd805029079dadea Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:19:27 +0200 Subject: [PATCH 68/87] test: cover quarantined queue action matrix --- tests/engineering/dashboard.spec.mjs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/engineering/dashboard.spec.mjs b/tests/engineering/dashboard.spec.mjs index 89aaa61b..455bb8ef 100644 --- a/tests/engineering/dashboard.spec.mjs +++ b/tests/engineering/dashboard.spec.mjs @@ -10115,7 +10115,7 @@ test.describe("Engineering Status browser smoke", () => { producer_type: "CLI", action_intent: "UNSPECIFIED", modified_at: "2026-08-02T10:01:00Z", - queue_source: "CENTRAL", + queue_source: "CENTRAL", disposition_revision: 3, queue_state: "DEFERRED", }, ], 1)); @@ -10138,7 +10138,7 @@ test.describe("Engineering Status browser smoke", () => { await page.evaluate(() => queueItems([{ submission_id: "sub-central-fifo", filename: "sub-central-fifo", title_kind: "producer_submission", producer_type: "CLI", action_intent: "UNSPECIFIED", - modified_at: "2026-08-02T10:01:00Z", queue_source: "CENTRAL", queue_state: "QUEUED", + modified_at: "2026-08-02T10:01:00Z", queue_source: "CENTRAL", disposition_revision: 4, queue_state: "QUEUED", }], 1)); for (const [actionKey, titleKey] of [ ["queue.defer_action", "queue.defer_title"], @@ -10156,6 +10156,13 @@ test.describe("Engineering Status browser smoke", () => { await expect(decline).toHaveClass(/queue-defer--destructive/); await expect(decline).toHaveAttribute("title", messages["queue.decline_action"]); await expect(decline).toHaveAttribute("aria-label", messages["queue.decline_action"]); + + await page.evaluate(() => queueItems([{ + submission_id: "sub-central-fifo", filename: "sub-central-fifo", title_kind: "producer_submission", + producer_type: "CLI", action_intent: "UNSPECIFIED", modified_at: "2026-08-02T10:01:00Z", + queue_source: "CENTRAL", disposition_revision: 5, queue_state: "QUARANTINED", + }], 1)); + expect(await page.locator("#queueList button").allTextContents()).toEqual([messages["queue.resume_action"], messages["queue.decline_action"]]); }); test("keeps a waiting Inbox item when deferring is cancelled", async ({ page }) => { From 493dca7c489d153f0740df5b4aa658f15d2f1417 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:25:12 +0200 Subject: [PATCH 69/87] fix: harden queue disposition authority --- src/engineering_platform/server.py | 73 ++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 3b70216c..df589904 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -92,7 +92,7 @@ # bootstrap is deliberately separate from the retired predecessor migration # machinery: it creates a clean installation only and never accepts a source # database path. -SERVER_STORE_SCHEMA_VERSION = 55 +SERVER_STORE_SCHEMA_VERSION = 56 SERVER_ENVIRONMENT_DATA_ROOT = "EP_SERVER_DATA_ROOT" FILE_INBOX_DIRECTORY = "file-inbox" HTTP_JSON_OPENAPI_PATH = "/v1/openapi.json" @@ -1209,12 +1209,23 @@ def _migrate_schema_55(connection: sqlite3.Connection) -> None: connection.execute("DROP TABLE ep_installations_schema54") connection.execute("ALTER TABLE ep_submissions ADD COLUMN disposition_revision INTEGER NOT NULL DEFAULT 0") connection.execute("CREATE TABLE ep_queue_disposition_operations (operation_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), actor_reference TEXT NOT NULL, command_digest TEXT NOT NULL, from_state TEXT NOT NULL, to_state TEXT NOT NULL, previous_revision INTEGER NOT NULL, resulting_revision INTEGER NOT NULL, event_id INTEGER NOT NULL REFERENCES ep_submission_events(event_id), recorded_at TEXT NOT NULL)") - connection.execute("CREATE TABLE ep_operator_capabilities (consumer_id TEXT NOT NULL, project_id TEXT NOT NULL, capability TEXT NOT NULL CHECK(capability IN ('QUEUE_HOLD_RESUME','QUEUE_DECLINE')), granted_at TEXT NOT NULL, revoked_at TEXT, PRIMARY KEY(consumer_id,project_id,capability))") connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(55)") connection.execute("UPDATE engineering_metadata SET value='55' WHERE key='installation.schema_version'") connection.execute("UPDATE ep_installations SET schema_version=55") +def _migrate_schema_56(connection: sqlite3.Connection) -> None: + """Add explicitly granted, project-scoped queue operator capabilities.""" + connection.execute("ALTER TABLE ep_installations RENAME TO ep_installations_schema55") + connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56)))") + connection.execute("INSERT INTO ep_installations SELECT instance_id,created_at,56 FROM ep_installations_schema55") + connection.execute("DROP TABLE ep_installations_schema55") + connection.execute("CREATE TABLE IF NOT EXISTS ep_operator_capabilities (consumer_id TEXT NOT NULL, project_id TEXT NOT NULL, capability TEXT NOT NULL CHECK(capability IN ('QUEUE_HOLD_RESUME','QUEUE_DECLINE')), granted_at TEXT NOT NULL, revoked_at TEXT, PRIMARY KEY(consumer_id,project_id,capability))") + connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(56)") + connection.execute("UPDATE engineering_metadata SET value='56' WHERE key='installation.schema_version'") + connection.execute("UPDATE ep_installations SET schema_version=56") + + def validate_store(data_root: Path, identity: RuntimeIdentity) -> dict[str, object]: """Return a deterministic fail-closed current-schema structural report.""" path = data_root / SERVER_DATABASE_FILENAME @@ -1280,14 +1291,14 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int existing_tables = _table_names(existing) if existing_tables: current_schema = _schema_version(existing) - if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, SERVER_STORE_SCHEMA_VERSION}: + if current_schema not in {41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, SERVER_STORE_SCHEMA_VERSION}: raise ServerConfigurationError( f"EP Server store is not a valid official schema-{SERVER_STORE_SCHEMA_VERSION} installation." ) if current_schema == SERVER_STORE_SCHEMA_VERSION: validate_store(data_root, identity) return identity - if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54}: + if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55}: with sqlite3.connect(database_path) as connection: # Schema-49 rebuilds the submission parent table # to widen its immutable transport constraint. @@ -1316,9 +1327,12 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_52(connection) if current_schema in {42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52}: _migrate_schema_53(connection) - if current_schema != 54: + if current_schema < 54: _migrate_schema_54(connection) - _migrate_schema_55(connection) + if current_schema < 55: + _migrate_schema_55(connection) + if current_schema < 56: + _migrate_schema_56(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") validate_store(data_root, identity) @@ -1347,6 +1361,7 @@ def initialize(data_root: Path, *, bind_host: str = "127.0.0.1", bind_port: int _migrate_schema_53(connection) _migrate_schema_54(connection) _migrate_schema_55(connection) + _migrate_schema_56(connection) connection.execute("COMMIT") connection.execute("PRAGMA legacy_alter_table=OFF") connection.execute("PRAGMA foreign_keys=ON") @@ -2576,6 +2591,31 @@ def _operator_capability(connection: sqlite3.Connection, token: object, project_ return actor if grant is not None else "" +def _same_origin(headers: Mapping[str, str]) -> bool: + """Accept only an absent or same-host HTTP(S) browser origin. + + Origin is CSRF protection, never an authentication substitute. The + capability check at the mutation boundary remains authoritative. + """ + origin, host = headers.get("Origin", "") or "", headers.get("Host", "") or "" + return origin in {"", f"http://{host}", f"https://{host}"} + + +def _strict_json_object(raw: bytes) -> dict[str, object]: + """Decode one JSON object while rejecting duplicate member names.""" + def no_duplicates(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError("duplicate JSON member") + result[key] = value + return result + value = json.loads(raw.decode("utf-8"), object_pairs_hook=no_duplicates) + if not isinstance(value, dict): + raise ValueError("JSON object required") + return value + + def _admit_server_owned_file_inbox( data_root: Path, envelope: dict[str, object], receipt_id: str, received_at: str, ) -> dict[str, object]: @@ -3333,7 +3373,7 @@ def _delegate_dashboard(self, method: str) -> None: self._send(200, result) return if method == "do_POST" and request.path == "/api/queue-disposition": - if self.headers.get("Origin") not in {None, "", f"http://{self.headers.get('Host', '')}"}: + if not _same_origin(self.headers): self._send(403, {"error": "INVALID_ORIGIN"}) return if not isinstance(selected, str) or selected not in project_ids: @@ -3343,7 +3383,7 @@ def _delegate_dashboard(self, method: str) -> None: length = int(self.headers.get("Content-Length", "0")) if not 2 <= length <= 1024: raise ValueError - payload = json.loads(self.rfile.read(length).decode("utf-8")) + payload = _strict_json_object(self.rfile.read(length)) expected = {"contract_version", "operation_id", "submission_id", "expected_state", "expected_revision", "disposition", "reason"} if (not isinstance(payload, dict) or set(payload) != expected or payload.get("contract_version") != "1.0" @@ -3747,7 +3787,7 @@ def health(data_root: Path) -> dict[str, object]: def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="engineering-platform-server", description="Manage the standalone Engineering Platform Server foundation") - parser.add_argument("command", choices=("init", "start", "serve", "stop", "status", "health", "service-install", "service-uninstall", "relay-install", "relay-uninstall", "pairing-create", "agent-status", "agent-revoke", "agent-reset", "topology", "submission-diagnose", "bootstrap-topology", "register-topology", "provision-declaration", "issue-consumer-credential", "bind-repository", "rebind-repository", "unbind-repository", "resolve-repository", "register-producer-binding", "list-producer-bindings", "deactivate-producer-binding")) + parser.add_argument("command", choices=("init", "start", "serve", "stop", "status", "health", "service-install", "service-uninstall", "relay-install", "relay-uninstall", "pairing-create", "agent-status", "agent-revoke", "agent-reset", "topology", "submission-diagnose", "bootstrap-topology", "register-topology", "provision-declaration", "issue-consumer-credential", "grant-operator-capability", "revoke-operator-capability", "bind-repository", "rebind-repository", "unbind-repository", "resolve-repository", "register-producer-binding", "list-producer-bindings", "deactivate-producer-binding")) parser.add_argument("--data-root", type=Path, default=default_data_root()) parser.add_argument("--bind-host", default="127.0.0.1") parser.add_argument("--bind-port", type=int, default=8765) @@ -3763,6 +3803,7 @@ def build_parser() -> argparse.ArgumentParser: parser.add_argument("--external-resource-identity") parser.add_argument("--binding-id") parser.add_argument("--reason") + parser.add_argument("--capability", choices=("QUEUE_HOLD_RESUME", "QUEUE_DECLINE")) return parser @@ -3844,6 +3885,20 @@ def main(argv: list[str] | None = None) -> int: from .submission_service import issue_consumer_credential with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection: result = issue_consumer_credential(connection, consumer_id=args.consumer_id, project_id=args.project_id) + elif args.command in {"grant-operator-capability", "revoke-operator-capability"}: + if not args.project_id or not args.consumer_id or not args.capability: + raise ServerConfigurationError("--project-id, --consumer-id and --capability are required for queue operator capability management.") + initialize(args.data_root) + with sqlite3.connect(args.data_root / SERVER_DATABASE_FILENAME) as connection: + if args.command == "grant-operator-capability": + registered = connection.execute("SELECT 1 FROM ep_consumer_registrations WHERE consumer_id=? AND project_id=? AND status='ACTIVE'", (args.consumer_id, args.project_id)).fetchone() + if registered is None: + raise ServerConfigurationError("ACTIVE_SCOPED_CONSUMER_REQUIRED") + connection.execute("INSERT INTO ep_operator_capabilities(consumer_id,project_id,capability,granted_at,revoked_at) VALUES(?,?,?,?,NULL) ON CONFLICT(consumer_id,project_id,capability) DO UPDATE SET granted_at=excluded.granted_at,revoked_at=NULL", (args.consumer_id, args.project_id, args.capability, _utcnow())) + result = {"result": "GRANTED", "consumer_id": args.consumer_id, "project_id": args.project_id, "capability": args.capability} + else: + changed = connection.execute("UPDATE ep_operator_capabilities SET revoked_at=? WHERE consumer_id=? AND project_id=? AND capability=? AND revoked_at IS NULL", (_utcnow(), args.consumer_id, args.project_id, args.capability)).rowcount + result = {"result": "REVOKED" if changed else "NOT_ACTIVE", "consumer_id": args.consumer_id, "project_id": args.project_id, "capability": args.capability} elif args.command == "register-producer-binding": if not all((args.producer_type, args.external_resource_type, args.external_resource_identity, args.project_id, args.repository_id, args.reason)): raise ServerConfigurationError("--producer-type, --external-resource-type, --external-resource-identity, --project-id, --repository-id and --reason are required for producer binding registration.") From bf484db891c7d40d6ff64fe6e5643192c08646aa Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:25:56 +0200 Subject: [PATCH 70/87] docs: define queue operator capability boundary --- docs/engineering/EXECUTION_HOST_ARCHITECTURE.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md index bf104838..68a1d7a7 100644 --- a/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md +++ b/docs/engineering/EXECUTION_HOST_ARCHITECTURE.md @@ -91,7 +91,12 @@ internals, mutate Forge storage, or infer cancellation. Until a versioned Forge callback contract exists, readback is the required reconciliation path. Queue commands carry a versioned operation ID, expected state and monotone revision, and are arbitrated with the worker claim in CENTRAL. Origin checking -is CSRF protection only, never operator authentication. +is CSRF protection only, never operator authentication. The authenticated +consumer must additionally hold an active, project-scoped capability: +`QUEUE_HOLD_RESUME` for defer/quarantine/resume and `QUEUE_DECLINE` for the +terminal decline transition. Consumer credentials do not imply either grant. +The installation owner grants or revokes this narrow capability with the +Server CLI; it is not a producer-facing or dashboard-managed role system. The active mutation lease starts with the accepted execution and is retained through provider work, validation, delivery, finalization and reconciliation. It is released only after terminal From 330db3d33fa8c7d8ba9972e2561ceed55b351466 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:27:32 +0200 Subject: [PATCH 71/87] fix: make producer readback v1.2 strict --- .../schemas/producer-readback-v1.2.schema.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/schemas/producer-readback-v1.2.schema.json b/src/engineering_platform/schemas/producer-readback-v1.2.schema.json index 25f86a13..3e2fbd4d 100644 --- a/src/engineering_platform/schemas/producer-readback-v1.2.schema.json +++ b/src/engineering_platform/schemas/producer-readback-v1.2.schema.json @@ -3,10 +3,14 @@ "$id": "https://engineering-platform.local/schemas/producer-readback-v1.2.schema.json", "title": "Engineering Platform producer readback v1.2 disposition extension", "type": "object", - "required": ["contract_version", "submission", "disposition", "run", "result", "evidence"], + "additionalProperties": false, + "required": ["contract_version", "submission", "producer", "correlation", "provenance", "disposition", "run", "result", "evidence"], "properties": { "contract_version": {"const": "1.2"}, - "submission": {"type": "object", "required": ["state"]}, + "submission": {"type": "object", "additionalProperties": false, "required": ["id", "project_id", "repository_id", "state", "admission", "transport", "created_at", "accepted_request_digest"]}, + "producer": {"type": "object", "additionalProperties": false, "required": ["id", "type", "version"]}, + "correlation": {"type": "object", "additionalProperties": false, "required": ["correlation_id", "mission_id", "engineering_action_id"]}, + "provenance": {"type": "object", "additionalProperties": false, "required": ["status", "forge_execution"]}, "disposition": { "type": "object", "additionalProperties": false, @@ -24,7 +28,7 @@ } }, "run": {"type": ["object", "null"]}, - "result": {"type": "object"}, - "evidence": {"type": "object"} + "result": {"type": "object", "additionalProperties": false, "required": ["outcome", "terminal", "delivery_qualified"]}, + "evidence": {"type": "object", "additionalProperties": false, "required": ["status", "terminal_artifact", "repository"]} } } From d19309834cbf8c00132e417d2feeeaf821797944 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:29:14 +0200 Subject: [PATCH 72/87] fix: recover interrupted queue schema migration --- src/engineering_platform/server.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index df589904..338d2bf7 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -1184,7 +1184,20 @@ def _migrate_schema_54(connection: sqlite3.Connection) -> None: prompt TEXT NOT NULL, prompt_digest TEXT NOT NULL, constraints TEXT NOT NULL, idempotency_key TEXT, correlation_id TEXT, mission_id TEXT, engineering_action_id TEXT, transport_receipt_id TEXT, transport_received_at TEXT, state TEXT NOT NULL CHECK(state IN ('QUEUED','REJECTED','DEFERRED','QUARANTINED','DECLINED')), admission TEXT NOT NULL, created_at TEXT NOT NULL)""") - connection.execute("INSERT INTO ep_submissions SELECT * FROM ep_submissions_schema53") + # Do not use ``SELECT *`` here: an interrupted/newer installation can + # retain later additive columns while its recorded schema is still being + # recovered. Schema-54 owns exactly these predecessor columns. + connection.execute("""INSERT INTO ep_submissions( + submission_id,project_id,repository_id,producer_id,producer_type, + producer_version,transport,prompt,prompt_digest,constraints, + idempotency_key,correlation_id,mission_id,engineering_action_id, + transport_receipt_id,transport_received_at,state,admission,created_at + ) SELECT + submission_id,project_id,repository_id,producer_id,producer_type, + producer_version,transport,prompt,prompt_digest,constraints, + idempotency_key,correlation_id,mission_id,engineering_action_id, + transport_receipt_id,transport_received_at,state,admission,created_at + FROM ep_submissions_schema53""") connection.execute("CREATE TABLE ep_submission_events (event_id INTEGER PRIMARY KEY, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), event_kind TEXT NOT NULL, payload TEXT NOT NULL, recorded_at TEXT NOT NULL)") connection.execute("INSERT INTO ep_submission_events SELECT * FROM ep_submission_events_schema53") connection.execute("CREATE TABLE ep_submission_prompt_history (submission_id TEXT PRIMARY KEY REFERENCES ep_submissions(submission_id), prompt_digest TEXT NOT NULL, recorded_at TEXT NOT NULL)") @@ -1207,8 +1220,10 @@ def _migrate_schema_55(connection: sqlite3.Connection) -> None: connection.execute("CREATE TABLE ep_installations (instance_id TEXT PRIMARY KEY, created_at TEXT NOT NULL, schema_version INTEGER NOT NULL CHECK(schema_version IN (41,42,43,44,45,46,47,48,49,50,51,52,53,54,55)))") connection.execute("INSERT INTO ep_installations SELECT instance_id,created_at,55 FROM ep_installations_schema54") connection.execute("DROP TABLE ep_installations_schema54") - connection.execute("ALTER TABLE ep_submissions ADD COLUMN disposition_revision INTEGER NOT NULL DEFAULT 0") - connection.execute("CREATE TABLE ep_queue_disposition_operations (operation_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), actor_reference TEXT NOT NULL, command_digest TEXT NOT NULL, from_state TEXT NOT NULL, to_state TEXT NOT NULL, previous_revision INTEGER NOT NULL, resulting_revision INTEGER NOT NULL, event_id INTEGER NOT NULL REFERENCES ep_submission_events(event_id), recorded_at TEXT NOT NULL)") + columns = {str(row[1]) for row in connection.execute("PRAGMA table_info(ep_submissions)")} + if "disposition_revision" not in columns: + connection.execute("ALTER TABLE ep_submissions ADD COLUMN disposition_revision INTEGER NOT NULL DEFAULT 0") + connection.execute("CREATE TABLE IF NOT EXISTS ep_queue_disposition_operations (operation_id TEXT PRIMARY KEY, project_id TEXT NOT NULL, submission_id TEXT NOT NULL REFERENCES ep_submissions(submission_id), actor_reference TEXT NOT NULL, command_digest TEXT NOT NULL, from_state TEXT NOT NULL, to_state TEXT NOT NULL, previous_revision INTEGER NOT NULL, resulting_revision INTEGER NOT NULL, event_id INTEGER NOT NULL REFERENCES ep_submission_events(event_id), recorded_at TEXT NOT NULL)") connection.execute("INSERT OR IGNORE INTO engineering_schema_migrations(version) VALUES(55)") connection.execute("UPDATE engineering_metadata SET value='55' WHERE key='installation.schema_version'") connection.execute("UPDATE ep_installations SET schema_version=55") From 800ba5d00389260e300cfcd3aaf332f91a5cbb99 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:34:04 +0200 Subject: [PATCH 73/87] test: qualify versioned queue disposition contract --- .../http_json_postman_contract.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/qualification/http_json_postman_contract.py b/tools/qualification/http_json_postman_contract.py index 1d301305..d71526f2 100644 --- a/tools/qualification/http_json_postman_contract.py +++ b/tools/qualification/http_json_postman_contract.py @@ -127,6 +127,11 @@ def _queue_action_contract(data_root: Path, base_url: str) -> None: credential = str(submission_service.issue_consumer_credential( connection, consumer_id="postman-queue", project_id=project, )["credential"]) + for capability in ("QUEUE_HOLD_RESUME", "QUEUE_DECLINE"): + connection.execute( + "INSERT INTO ep_operator_capabilities(consumer_id,project_id,capability,granted_at) VALUES(?,?,?,?)", + ("postman-queue", project, capability, "qualification"), + ) payload = { "repository_id": repository, "producer": {"id": "postman-queue", "type": "HUMAN", "version": "1"}, @@ -140,22 +145,31 @@ def _queue_action_contract(data_root: Path, base_url: str) -> None: with urlopen(submit, timeout=3) as response: # nosec B310 submission_id = str(json.loads(response.read())["submission_id"]) + expected_state, expected_revision = "QUEUED", 0 + def action(disposition: str, reason: str, *, origin: str | None = None) -> tuple[int, str]: + nonlocal expected_state, expected_revision request = Request( base_url + f"/api/queue-disposition?project={project}", - data=json.dumps({"submission_id": submission_id, "disposition": disposition, "reason": reason}).encode(), - method="POST", headers={"Content-Type": "application/json", "Origin": origin or base_url}, + data=json.dumps({"contract_version": "1.0", "operation_id": f"postman-{expected_revision + 1}", + "submission_id": submission_id, "expected_state": expected_state, + "expected_revision": expected_revision, "disposition": disposition, + "reason": reason}).encode(), + method="POST", headers={"Content-Type": "application/json", "Origin": origin or base_url, + "Authorization": f"Bearer {credential}"}, ) try: with urlopen(request, timeout=3) as response: # nosec B310 - response.read() + response_payload = json.loads(response.read()) + expected_state = str(response_payload["state"]) + expected_revision = int(response_payload["resulting_revision"]) return response.status, "" except HTTPError as error: return error.code, error.read().decode("utf-8", "replace") for disposition, reason in ( ("DEFERRED", "Postman defer contract"), ("QUEUED", "Postman resume contract"), - ("QUARANTINED", "Postman quarantine contract"), ("QUEUED", "Postman resume after quarantine contract"), + ("QUARANTINED", "Postman quarantine contract"), ("DECLINED", "Postman decline contract"), ): status, detail = action(disposition, reason) From 34b9b3441d76bb9d47a030be411624363aea40f3 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 16:41:24 +0200 Subject: [PATCH 74/87] test: cover queue disposition boundary helpers --- tests/engineering/test_server_foundation.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/engineering/test_server_foundation.py b/tests/engineering/test_server_foundation.py index b485ea16..b7799c88 100644 --- a/tests/engineering/test_server_foundation.py +++ b/tests/engineering/test_server_foundation.py @@ -47,6 +47,25 @@ def test_empty_installation_bootstraps_a_clean_valid_store(self) -> None: self.assertFalse(report["running"]) self.assertFalse((self.root / ".engineering").exists()) + def test_queue_disposition_http_helpers_reject_ambiguous_json_and_foreign_origins(self) -> None: + """The queue mutation boundary treats origin and JSON ambiguity safely.""" + self.assertTrue(server._same_origin({"Host": "localhost:8765"})) + self.assertTrue(server._same_origin({"Host": "localhost:8765", "Origin": "https://localhost:8765"})) + self.assertFalse(server._same_origin({"Host": "localhost:8765", "Origin": "https://other.example"})) + self.assertEqual(server._strict_json_object(b'{"operation_id":"once"}'), {"operation_id": "once"}) + with self.assertRaises(ValueError): + server._strict_json_object(b'{"operation_id":"first","operation_id":"second"}') + with self.assertRaises(ValueError): + server._strict_json_object(b'[]') + + def test_queue_operator_capability_commands_are_explicit(self) -> None: + parsed = server.build_parser().parse_args(( + "grant-operator-capability", "--project-id", "project", "--consumer-id", "operator", + "--capability", "QUEUE_DECLINE", + )) + self.assertEqual((parsed.command, parsed.project_id, parsed.consumer_id, parsed.capability), + ("grant-operator-capability", "project", "operator", "QUEUE_DECLINE")) + def test_execution_runtime_status_preserves_the_virtual_environment_launcher(self) -> None: launcher = Path(self.temporary.name) / "venv" / "bin" / "python" launcher.parent.mkdir(parents=True) From 85bcb6af5c30b595d67d2461e7cfca5fb706b8b0 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 17:43:54 +0200 Subject: [PATCH 75/87] test: verify durable queue capability grants --- tests/engineering/test_server_foundation.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/engineering/test_server_foundation.py b/tests/engineering/test_server_foundation.py index b7799c88..f1dc8278 100644 --- a/tests/engineering/test_server_foundation.py +++ b/tests/engineering/test_server_foundation.py @@ -66,6 +66,22 @@ def test_queue_operator_capability_commands_are_explicit(self) -> None: self.assertEqual((parsed.command, parsed.project_id, parsed.consumer_id, parsed.capability), ("grant-operator-capability", "project", "operator", "QUEUE_DECLINE")) + def test_queue_operator_capability_grant_and_revoke_are_durable(self) -> None: + server.initialize(self.root) + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + server.project_topology.register_server_local_topology(connection, declaration={ + "schema_version": "1.0", "project": {"id": "queue-project", "authority_repository_id": "queue-repository"}, + "repository": {"id": "queue-repository", "role": "authority"}, "validation": {"kind": "none"}, + }) + from engineering_platform.submission_service import issue_consumer_credential + issue_consumer_credential(connection, consumer_id="queue-operator", project_id="queue-project") + arguments = ("--data-root", str(self.root), "--project-id", "queue-project", "--consumer-id", "queue-operator", "--capability", "QUEUE_DECLINE") + with redirect_stdout(io.StringIO()): + self.assertEqual(server.main(("grant-operator-capability", *arguments)), 0) + self.assertEqual(server.main(("revoke-operator-capability", *arguments)), 0) + with sqlite3.connect(self.root / server.SERVER_DATABASE_FILENAME) as connection: + self.assertIsNotNone(connection.execute("SELECT revoked_at FROM ep_operator_capabilities WHERE consumer_id='queue-operator' AND project_id='queue-project' AND capability='QUEUE_DECLINE'").fetchone()[0]) + def test_execution_runtime_status_preserves_the_virtual_environment_launcher(self) -> None: launcher = Path(self.temporary.name) / "venv" / "bin" / "python" launcher.parent.mkdir(parents=True) From e47c3a1b5720ce61704e3ac80bf7c75727f055ce Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 17:44:31 +0200 Subject: [PATCH 76/87] test: cover provider process recovery boundary --- tests/engineering/test_execution_host.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index c96f274b..b4c52829 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1623,6 +1623,21 @@ def test_optional_phase_telemetry_and_agent_timing_never_change_run_authority(se agent.last_execution_seconds = 1.2345 self.assertEqual(runner._record_agent_execution_time(state).agent_execution_seconds, 1.234) + def test_provider_process_boundary_records_only_a_verified_recovery_process(self) -> None: + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), FakeAgent(AgentResult("WAITING")), lambda _: None) + state = TransactionState("provider-process", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT") + with patch("engineering_platform.execution_host.write_runner_process") as written, \ + patch.object(runner, "_recovery_state", return_value=None), \ + patch("engineering_platform.execution_host.record_provider_started") as started: + runner._provider_process_boundary(state, {"pid": 42, "process_group": 42}) + written.assert_called_once_with(self.root, state.run_id, {"pid": 42, "process_group": 42}) + started.assert_not_called() + with patch("engineering_platform.execution_host.write_runner_process"), \ + patch.object(runner, "_recovery_state", return_value={"state": "RECOVERY_STARTING", "process_receipt_id": "receipt-1"}), \ + patch("engineering_platform.execution_host.record_provider_started") as started: + runner._provider_process_boundary(state, {"pid": 42, "process_group": 42}) + started.assert_called_once_with(self.root, run_id=state.run_id, receipt_id="receipt-1", pid=42, process_group=42, central_database=runner.store.central_database) + def test_optional_phase_wrappers_degrade_only_telemetry_storage_failures(self) -> None: with patch("engineering_platform.execution_host._start_phase", return_value=SimpleNamespace()) as start: self.assertIsNotNone(execution_host.start_phase(self.root, "phase-run", "VALIDATION")) From eb95b834ce92e338ad1e71839de671e63407b07d Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 17:46:26 +0200 Subject: [PATCH 77/87] docs: specify queue disposition contract --- .../EP_PRODUCER_READBACK_CONTRACT.md | 26 ++++++++++++++++++- .../http_json_postman_contract.py | 24 ++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md b/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md index 25379ac7..fc1707de 100644 --- a/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md +++ b/docs/engineering/EP_PRODUCER_READBACK_CONTRACT.md @@ -59,7 +59,31 @@ is present in the checkpoint's verified commit evidence. `VALIDATION_ONLY`, they are not fabricated into successful delivery. The machine-readable response shape is -[`producer-readback-v1.schema.json`](../../src/engineering_platform/schemas/producer-readback-v1.schema.json). +[`producer-readback-v1.2.schema.json`](../../src/engineering_platform/schemas/producer-readback-v1.2.schema.json). + +## Operations Console queue disposition + +The Console-only mutation boundary is separate from the producer API: + +``` +POST /api/queue-disposition?project={project_id} +Authorization: Bearer +``` + +The exact JSON v1.0 request has `contract_version`, a unique `operation_id`, +`submission_id`, `expected_state`, integer `expected_revision`, `disposition` +and a bounded operator `reason`. It rejects duplicate JSON names, unknown or +missing fields, stale state/revision, unknown submissions and invalid state +transitions. Replaying the same operation ID and exact command is idempotent; +using that ID for a different command is a conflict. + +Authentication and queue authority are deliberately distinct. A scoped +producer credential receives `403 OPERATOR_CAPABILITY_REQUIRED`; no credential +receives `401 UNAUTHENTICATED`. `QUEUE_HOLD_RESUME` permits defer, quarantine +and resume. `QUEUE_DECLINE` permits terminal decline, including the direct +`QUARANTINED -> DECLINED` path. The worker claim and this command arbitrate in +one CENTRAL transaction, so a claimed submission returns `409` and is never +cancelled by a queue action. ## Forge consumer mapping fixture diff --git a/tools/qualification/http_json_postman_contract.py b/tools/qualification/http_json_postman_contract.py index d71526f2..1a46d6d3 100644 --- a/tools/qualification/http_json_postman_contract.py +++ b/tools/qualification/http_json_postman_contract.py @@ -127,11 +127,9 @@ def _queue_action_contract(data_root: Path, base_url: str) -> None: credential = str(submission_service.issue_consumer_credential( connection, consumer_id="postman-queue", project_id=project, )["credential"]) - for capability in ("QUEUE_HOLD_RESUME", "QUEUE_DECLINE"): - connection.execute( - "INSERT INTO ep_operator_capabilities(consumer_id,project_id,capability,granted_at) VALUES(?,?,?,?)", - ("postman-queue", project, capability, "qualification"), - ) + producer_credential = str(submission_service.issue_consumer_credential( + connection, consumer_id="postman-producer", project_id=project, + )["credential"]) payload = { "repository_id": repository, "producer": {"id": "postman-queue", "type": "HUMAN", "version": "1"}, @@ -147,7 +145,8 @@ def _queue_action_contract(data_root: Path, base_url: str) -> None: expected_state, expected_revision = "QUEUED", 0 - def action(disposition: str, reason: str, *, origin: str | None = None) -> tuple[int, str]: + def action(disposition: str, reason: str, *, origin: str | None = None, + bearer: str | None = credential) -> tuple[int, str]: nonlocal expected_state, expected_revision request = Request( base_url + f"/api/queue-disposition?project={project}", @@ -156,7 +155,7 @@ def action(disposition: str, reason: str, *, origin: str | None = None) -> tuple "expected_revision": expected_revision, "disposition": disposition, "reason": reason}).encode(), method="POST", headers={"Content-Type": "application/json", "Origin": origin or base_url, - "Authorization": f"Bearer {credential}"}, + **({"Authorization": f"Bearer {bearer}"} if bearer else {})}, ) try: with urlopen(request, timeout=3) as response: # nosec B310 @@ -167,6 +166,17 @@ def action(disposition: str, reason: str, *, origin: str | None = None) -> tuple except HTTPError as error: return error.code, error.read().decode("utf-8", "replace") + for bearer, expected_status in ((None, 401), (producer_credential, 403)): + status, detail = action("DEFERRED", "Capability negative contract", bearer=bearer) + if status != expected_status: + raise RuntimeError(f"POSTMAN_QUEUE_AUTHORIZATION_FAILED:{expected_status}:{status}:{detail}") + with sqlite3.connect(data_root / server.SERVER_DATABASE_FILENAME) as connection: + for capability in ("QUEUE_HOLD_RESUME", "QUEUE_DECLINE"): + connection.execute( + "INSERT INTO ep_operator_capabilities(consumer_id,project_id,capability,granted_at) VALUES(?,?,?,?)", + ("postman-queue", project, capability, "qualification"), + ) + for disposition, reason in ( ("DEFERRED", "Postman defer contract"), ("QUEUED", "Postman resume contract"), ("QUARANTINED", "Postman quarantine contract"), From 51523e87562f3fcfdf3bc266751100eb21e9abe9 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 17:47:00 +0200 Subject: [PATCH 78/87] refactor: centralize queue transition policy --- src/engineering_platform/submission_service.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/submission_service.py b/src/engineering_platform/submission_service.py index cc104ef1..90cc9d72 100644 --- a/src/engineering_platform/submission_service.py +++ b/src/engineering_platform/submission_service.py @@ -26,6 +26,11 @@ VALID_TRANSPORTS = frozenset({"HTTP", "CLI", "FILE_INBOX", "DEPENDABOT", "LEGACY_FILE"}) VALID_EXECUTION_MODES = frozenset({"MANAGED", "GENESIS"}) OPERATOR_QUEUE_STATES = frozenset({"DEFERRED", "QUARANTINED", "DECLINED"}) +OPERATOR_QUEUE_TRANSITIONS = { + "QUEUED": frozenset({"DEFERRED", "QUARANTINED", "DECLINED"}), + "DEFERRED": frozenset({"QUEUED"}), + "QUARANTINED": frozenset({"QUEUED", "DECLINED"}), +} # This is the complete B8D lifecycle. The final value deliberately says what # CENTRAL has *not* done: admission makes a submission eligible for a later @@ -89,10 +94,7 @@ def operator_queue_disposition(connection: sqlite3.Connection, *, project_id: st raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) if connection.execute("SELECT 1 FROM ep_parity_lifecycle_dispatches WHERE submission_id=?", (submission_id,)).fetchone() is not None: raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) - allowed = (current == "QUEUED" and disposition in OPERATOR_QUEUE_STATES) or ( - current in {"DEFERRED", "QUARANTINED"} and disposition == "QUEUED" - ) or (current == "QUARANTINED" and disposition == "DECLINED") - if not allowed: + if disposition not in OPERATOR_QUEUE_TRANSITIONS.get(current, frozenset()): raise SubmissionError("QUEUE_DISPOSITION_CONFLICT", 409) now = _now() changed = connection.execute("UPDATE ep_submissions SET state=?,disposition_revision=disposition_revision+1 WHERE project_id=? AND submission_id=? AND state=? AND disposition_revision=?", (disposition, project_id, submission_id, current, revision)).rowcount From 3626eed677e2fa3dd3056a77086d7dcae36dda07 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 18:02:50 +0200 Subject: [PATCH 79/87] test: cover repair recovery evidence paths --- tests/engineering/test_execution_host.py | 67 ++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index b4c52829..7d8bfb08 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1638,6 +1638,38 @@ def test_provider_process_boundary_records_only_a_verified_recovery_process(self runner._provider_process_boundary(state, {"pid": 42, "process_group": 42}) started.assert_called_once_with(self.root, run_id=state.run_id, receipt_id="receipt-1", pid=42, process_group=42, central_database=runner.store.central_database) + def test_provider_invocation_records_only_safe_context_and_recovery_telemetry(self) -> None: + """Supplementary provider telemetry retains bounded context without authority.""" + agent = FakeAgent(AgentResult("WAITING")) + agent.last_context_escalations = ( + {"reason": "missing evidence", "boundary_kind": "run", "diagnostic": "safe diagnostic"}, + "untrusted", + ) + agent.last_execution_seconds = 2.0 + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), agent, lambda _: None) + state = TransactionState("provider-context", "pcvantol/djconnect", str(self.prompt), "EXECUTE_AGENT") + self.store.save(state) + with patch("engineering_platform.execution_host.persist_provider_invocation", return_value="provider-1") as persist: + invocation_id = runner._persist_provider_invocation( + state, phase="EXECUTE_AGENT", observed_metadata={"raw_provider_model": "gpt-5.6-terra"}, + ) + self.assertEqual(invocation_id, "provider-1") + recorded = persist.call_args.args[1] + self.assertEqual(recorded.model, "gpt-5.6-terra") + self.assertEqual(recorded.churn["context_scope_effective"], "INVESTIGATION") + self.assertEqual(recorded.churn["context_escalation_count"], 1) + self.assertEqual(recorded.churn["context_escalation_reasons"], "missing evidence") + + recovered = runner._recovery_record( + state, original="provider-1", replacement="provider-2", eligibility="ELIGIBLE", + result="RECOVERED", requested_at="then", started_at="then", completed_at="now", + ) + self.store.save(recovered) + agent.last_execution_seconds = 0.5 + merged = runner._record_agent_execution_time(state) + self.assertEqual(merged.provider_recovery_attempts, recovered.provider_recovery_attempts) + self.assertEqual(merged.agent_execution_seconds, 0.5) + def test_optional_phase_wrappers_degrade_only_telemetry_storage_failures(self) -> None: with patch("engineering_platform.execution_host._start_phase", return_value=SimpleNamespace()) as start: self.assertIsNotNone(execution_host.start_phase(self.root, "phase-run", "VALIDATION")) @@ -1659,6 +1691,41 @@ def test_repair_plans_and_environmental_validation_require_explicit_durable_evid self.assertFalse(runner._is_environmental_validation_instability(AgentResult("FAILED", validation_disposition="environmental_instability", validation_evidence=({"result": "passed"},)))) self.assertTrue(runner._is_environmental_validation_instability(AgentResult("FAILED", validation_disposition="environmental_instability", validation_evidence=({"result": "passed once; timed out once"},)))) + def test_repair_result_requires_the_durable_plan_and_preserves_its_bounded_pr(self) -> None: + """Recovery never infers a repair plan or lets a provider switch PRs.""" + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), FakeAgent(AgentResult("WAITING")), lambda _: None) + missing = TransactionState( + "repair-plan-missing", "pcvantol/djconnect", str(self.prompt), "REPAIR_AGENT", + branch="codex/repair-plan-missing", pull_request=17, repair_iterations=1, + ) + blocked = runner._advance_after_repair_agent_result( + missing, AgentResult("COMPLETE", branch=missing.branch, pull_request=17), + ) + self.assertEqual((blocked.phase, blocked.next_action), ("BLOCKED", "repair_plan_missing")) + self.assertTrue(self.store.load(missing.run_id).terminal) + + planned = missing.__class__(**{ + **missing.__dict__, + "run_id": "repair-plan-conflict", + "repair_audit": ({ + "iteration": "1", "outcome": "planned", "failed_checks": "required check", + "proposed_action": "repair only the required check", + },), + }) + conflict = runner._advance_after_repair_agent_result( + planned, AgentResult("COMPLETE", branch=planned.branch, pull_request=18, diagnostic="changed PR"), + ) + self.assertEqual((conflict.phase, conflict.next_action), ("BLOCKED", "bounded_scope_conflict")) + self.assertEqual(conflict.repair_audit[-1]["outcome"], "submitted_for_recheck") + self.assertEqual(self.store.load(planned.run_id).repair_audit, conflict.repair_audit) + + 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"), + ) + self.assertEqual((failed.phase, failed.next_action), ("FAILED", "external_action_required")) + self.assertEqual(failed.repair_audit[-1]["outcome"], "agent_failed") + def test_validation_failure_and_commit_evidence_helpers_refuse_unverified_inputs(self) -> None: runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), FakeAgent(AgentResult("WAITING")), lambda _: None) self.assertFalse(runner._has_failed_validation_evidence(AgentResult("FAILED"))) From db5b258ef49d87e38410f26f2f4c2c594e838b92 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 18:07:23 +0200 Subject: [PATCH 80/87] test: cover repair revalidation failure paths --- tests/engineering/test_execution_host.py | 54 ++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/engineering/test_execution_host.py b/tests/engineering/test_execution_host.py index 7d8bfb08..bfae6c17 100644 --- a/tests/engineering/test_execution_host.py +++ b/tests/engineering/test_execution_host.py @@ -1726,6 +1726,60 @@ def test_repair_result_requires_the_durable_plan_and_preserves_its_bounded_pr(se self.assertEqual((failed.phase, failed.next_action), ("FAILED", "external_action_required")) self.assertEqual(failed.repair_audit[-1]["outcome"], "agent_failed") + def test_repair_rereview_paths_fail_closed_for_missing_genesis_or_validation_evidence(self) -> None: + """A repair must return a candidate to the same assurance boundary.""" + runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), FakeAgent(AgentResult("WAITING")), lambda _: None) + + def planned(run_id: str, *, mode: str = "MANAGED", rounds: int = 1) -> TransactionState: + return TransactionState( + run_id, "pcvantol/djconnect", str(self.prompt), "REPAIR_AGENT", + branch="codex/repair-rereview", pull_request=17, execution_mode=mode, + repair_iterations=rounds, + repair_audit=({ + "iteration": str(rounds), "outcome": "planned", "failed_checks": "required check", + "proposed_action": "repair bounded finding", + },), + ) + + genesis = planned("genesis-repair-missing", mode="GENESIS") + no_candidate = runner._advance_after_repair_agent_result( + genesis, AgentResult("COMPLETE", branch=genesis.branch), + ) + self.assertEqual((no_candidate.phase, no_candidate.next_action), ("BLOCKED", "repair_candidate_unavailable")) + + target = self.root / "genesis-repair-candidate" + target.mkdir() + review_state = planned("genesis-repair-reviewed", mode="GENESIS") + reviewed_state = review_state.__class__(**{**review_state.__dict__, "phase": "WAIT_FOR_TERMINAL_EVIDENCE"}) + reconciled = TransactionState("genesis-repair-complete", "pcvantol/djconnect", str(self.prompt), "COMPLETE", terminal=True) + with patch.object(runner, "_run_quality_assurance", return_value=(reviewed_state, AgentResult("COMPLETE", repository_path=str(target)))) as review, \ + patch.object(runner, "_reconcile_genesis_result", return_value=reconciled) as reconcile: + advanced = runner._advance_after_repair_agent_result( + review_state, AgentResult("COMPLETE", branch=review_state.branch, repository_path=str(target)), + ) + self.assertIs(advanced, reconciled) + self.assertEqual(review.call_args.kwargs["assurance_root"], target) + reconcile.assert_called_once() + + exhausted = planned("managed-repair-validation-exhausted", rounds=3) + with patch.object( + runner, "_run_local_repository_validation", + side_effect=lambda state, _: (state, AgentResult("FAILED", validation_evidence=({"result": "failed"},))), + ): + blocked = runner._advance_after_repair_agent_result( + exhausted, AgentResult("COMPLETE", branch=exhausted.branch, pull_request=17), + ) + self.assertEqual((blocked.phase, blocked.next_action), ("BLOCKED", "repair_budget_exhausted")) + + inspected = planned("managed-repair-inspection-failure") + post_review = inspected.__class__(**{**inspected.__dict__, "phase": "WAIT_FOR_TERMINAL_EVIDENCE"}) + review_result = AgentResult("COMPLETE", branch=inspected.branch, pull_request=17) + with patch.object(runner, "_run_local_repository_validation", side_effect=lambda state, _: (state, review_result)), \ + patch.object(runner, "_run_quality_assurance", return_value=(post_review, review_result)), \ + patch.object(runner.repository, "inspect", side_effect=RunnerError("unavailable")): + unavailable = runner._advance_after_repair_agent_result(inspected, review_result) + self.assertEqual((unavailable.phase, unavailable.next_action), ("BLOCKED", "repair_candidate_unavailable")) + def test_validation_failure_and_commit_evidence_helpers_refuse_unverified_inputs(self) -> None: runner = EngineeringRunner(self.root, self.store, FakeRepository(), FakeGitHub([]), FakeAgent(AgentResult("WAITING")), lambda _: None) self.assertFalse(runner._has_failed_validation_evidence(AgentResult("FAILED"))) From f713bbd0a22ef65429d8b1b021fabc58ebcb4653 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 18:24:47 +0200 Subject: [PATCH 81/87] test: raise server contract coverage margin --- tests/engineering/test_server_foundation.py | 83 +++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/engineering/test_server_foundation.py b/tests/engineering/test_server_foundation.py index f1dc8278..1b03f0ce 100644 --- a/tests/engineering/test_server_foundation.py +++ b/tests/engineering/test_server_foundation.py @@ -58,6 +58,89 @@ def test_queue_disposition_http_helpers_reject_ambiguous_json_and_foreign_origin with self.assertRaises(ValueError): server._strict_json_object(b'[]') + def test_server_presentation_boundaries_reject_unsafe_headers_and_normalize_quota_data(self) -> None: + """Console-only helpers remain fail-closed for unsafe or malformed inputs.""" + self.assertEqual( + server._attachment_content_disposition("qualification-report.md"), + 'attachment; filename="qualification-report.md"', + ) + self.assertEqual( + server._report_content_disposition("run-123"), + 'attachment; filename="engineering-report-run-123.md"', + ) + for unsafe in (None, "report\nInjected: value", "../../report.md"): + with self.assertRaises(ValueError): + server._attachment_content_disposition(unsafe) + for unsafe_id in (None, "../run", "run\r\n"): + with self.assertRaises(ValueError): + server._report_content_disposition(unsafe_id) + + self.assertIsNone(server._remaining_rate_limit_capacity({})) + self.assertIsNone(server._remaining_rate_limit_capacity({"windows": ["malformed", {"used_percent": True}]})) + self.assertEqual(server._remaining_rate_limit_capacity({"windows": [{"used_percent": -4}, {"used_percent": 125}]}), 0.0) + normalized = server._normalize_rate_limits({ + "rateLimits": { + "primary": {"usedPercent": 19.7, "windowDurationMins": 300, "resetsAt": 12}, + "secondary": {"usedPercent": True, "windowDurationMins": 60, "resetsAt": 13}, + }, + "rateLimitResetCredits": {"availableCount": 2}, + }) + self.assertEqual(normalized, { + "windows": [{"label": "5-uursvenster", "used_percent": 20, "window_minutes": 300, "resets_at": 12}], + "reset_credits": 2, + }) + self.assertEqual(server._rate_limit_window_label(10_080), "Weekvenster") + self.assertEqual(server._rate_limit_window_label(2_880), "2-daags venster") + self.assertEqual(server._rate_limit_window_label(120), "2-uursvenster") + self.assertEqual(server._rate_limit_window_label(15), "15-minutenvenster") + + def test_github_rate_limit_projection_handles_unavailable_and_exhausted_resources(self) -> None: + with patch("engineering_platform.server.GitHubProvider") as provider: + provider.return_value.github.side_effect = RuntimeError("rate limit unavailable") + self.assertEqual(server._github_rate_limit_status(), {"limited": True}) + provider.return_value.github.side_effect = None + provider.return_value.github.return_value = json.dumps({"resources": {"core": {"remaining": 3}}}) + self.assertEqual(server._github_rate_limit_status(), {"limited": False}) + provider.return_value.github.return_value = json.dumps({ + "resources": { + "core": {"remaining": 0, "reset": 50}, + "graphql": {"remaining": -1, "reset": 20}, + "search": "malformed", + }, + }) + self.assertEqual(server._github_rate_limit_status(), {"limited": True, "reset_at": 20}) + + def test_component_log_query_contract_rejects_invalid_filters_before_central_sql(self) -> None: + query = server._parse_central_log_query({ + "page": ["2"], "page_size": ["25"], "start": ["2026-09-01"], "end": ["2026-09-02"], + "inclusive_end": ["1"], "search": ["%_term"], "level": ["warning"], + "event": [" run.finished ", "run.started", "run.started"], "sort": ["event"], "direction": ["asc"], + }) + self.assertEqual((query.page, query.page_size, query.level, query.events, query.sort_key, query.direction), + (2, 25, "WARNING", ("run.finished", "run.started"), "event", "asc")) + self.assertTrue(query.inclusive_end) + self.assertEqual(server._central_log_components("all")[0], PLATFORM_COMPONENT_IDS) # type: ignore[index] + self.assertEqual(server._central_log_components("ep_server")[1], ("ep_server",)) # type: ignore[index] + self.assertIsNone(server._central_log_components("not-a-component")) + for invalid in ( + {"page": ["zero"]}, {"page": ["0"]}, {"page_size": ["501"]}, + {"level": ["verbose"]}, {"search": ["x" * 161]}, {"event": ["x" * 161]}, + {"sort": ["unknown"]}, {"direction": ["sideways"]}, + ): + with self.assertRaises(ValueError): + server._parse_central_log_query(invalid) + + def test_local_only_provider_controls_fail_closed_before_starting_external_processes(self) -> None: + with patch("engineering_platform.server.sys.platform", "linux"): + with self.assertRaisesRegex(ValueError, "LOCAL_DIRECTORY_PICKER_UNAVAILABLE"): + server._choose_local_directory(self.root) + with self.assertRaisesRegex(ValueError, "local macOS Server"): + server._start_provider_login(self.root, "GITHUB") + with self.assertRaisesRegex(ValueError, "Unsupported provider login"): + server._start_provider_login(self.root, "OTHER") + with self.assertRaisesRegex(ValueError, "Unsupported provider logout"): + server._logout_provider(self.root, "OTHER") + def test_queue_operator_capability_commands_are_explicit(self) -> None: parsed = server.build_parser().parse_args(( "grant-operator-capability", "--project-id", "project", "--consumer-id", "operator", From f8495fe1150f5d5dbe1bfdc86fcdfab485c3907e Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 18:52:39 +0200 Subject: [PATCH 82/87] fix: reconcile canonical version qualification --- .../engineering-platform-validation.yml | 2 +- .../ep-server-production-release.yml | 2 +- .../test_platform_productization.py | 22 +++ tools/qualification/advance_platform_build.py | 53 +++++-- .../platform_version_consistency.py | 141 ++++++++++++------ 5 files changed, 160 insertions(+), 60 deletions(-) diff --git a/.github/workflows/engineering-platform-validation.yml b/.github/workflows/engineering-platform-validation.yml index 9b7547c3..c998571d 100644 --- a/.github/workflows/engineering-platform-validation.yml +++ b/.github/workflows/engineering-platform-validation.yml @@ -73,7 +73,7 @@ jobs: python3 -c "import engineering_platform, engineering_platform.execution_host" python3 -m compileall -q src/engineering_platform PYTHONPATH=src python3 tools/qualification/console_route_ownership_guard.py --source-root src - python3 tools/qualification/platform_version_consistency.py --source-root . + python3 tools/qualification/platform_version_consistency.py --source-root . --installed-python "$(command -v python3)" python3 -m unittest discover -s tests -p 'test_*.py' - name: Qualify installed P-TRANSPORT 3×2 ingress matrix run: python3 tools/qualification/p_transport_installed_ingress_matrix.py --source-root . diff --git a/.github/workflows/ep-server-production-release.yml b/.github/workflows/ep-server-production-release.yml index 6b2aa02c..ce8cd920 100644 --- a/.github/workflows/ep-server-production-release.yml +++ b/.github/workflows/ep-server-production-release.yml @@ -98,7 +98,7 @@ jobs: python3 -m pip install "dist/engineering_platform-${{ needs.release-context.outputs.version }}-py3-none-any.whl" python3 -m compileall -q src/engineering_platform PYTHONPATH=src python3 tools/qualification/console_route_ownership_guard.py --source-root src - python3 tools/qualification/platform_version_consistency.py --source-root . + python3 tools/qualification/platform_version_consistency.py --source-root . --installed-python "$(command -v python3)" python3 -m unittest discover -s tests -p 'test_*.py' python3 tools/qualification/p_transport_installed_ingress_matrix.py --source-root . # This uses an isolated CENTRAL root and local bare Git origin; the diff --git a/tests/engineering/test_platform_productization.py b/tests/engineering/test_platform_productization.py index b9ceba62..4bb3604a 100644 --- a/tests/engineering/test_platform_productization.py +++ b/tests/engineering/test_platform_productization.py @@ -196,9 +196,31 @@ def test_release_build_can_set_an_exact_branch_version_across_all_projections(se module.set_version(root, "2.1.6") self.assertEqual(module.set_version(root, "2.2.0"), "2.2.0") self.assertEqual(module._current_version(root), "2.2.0") + package_lock = json.loads((root / "package-lock.json").read_text(encoding="utf-8")) + self.assertEqual(package_lock["version"], "2.2.0") + self.assertEqual(package_lock["packages"][""]["version"], "2.2.0") with self.assertRaisesRegex(RuntimeError, "stable X.Y.Z"): module.set_version(root, "2.2") + def test_version_writer_rejects_invalid_last_projection_without_partial_update(self) -> None: + import importlib.util + script = ROOT / "tools" / "qualification" / "advance_platform_build.py" + spec = importlib.util.spec_from_file_location("advance_platform_build", script) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for relative in _version_projection_files(): + target = root / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, target) + pyproject_before = (root / "pyproject.toml").read_bytes() + (root / "src" / "engineering_platform" / "platform_version.py").write_text("# missing canonical constant\n", encoding="utf-8") + with self.assertRaisesRegex(RuntimeError, "runtime version projection drift"): + module.set_version(root, "2.2.1") + self.assertEqual((root / "pyproject.toml").read_bytes(), pyproject_before) + def test_public_api_has_all_productization_capabilities(self) -> None: registered = set(capabilities()) self.assertTrue({"runner", "runtime_provider", "repository_provider", "service_manager_provider", "remote_submission_provider", "private_remote_access_provider"} <= registered) diff --git a/tools/qualification/advance_platform_build.py b/tools/qualification/advance_platform_build.py index b7e23072..a6f673f0 100644 --- a/tools/qualification/advance_platform_build.py +++ b/tools/qualification/advance_platform_build.py @@ -51,7 +51,11 @@ def _atomic_write(path: Path, text: str) -> None: def _current_version(root: Path) -> str: root = root.resolve() try: - value = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"] + document = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) + project = document["project"] + if not isinstance(document, dict) or not isinstance(project, dict): + raise TypeError("[project] must be an object") + value = project["version"] except (OSError, KeyError, TypeError, tomllib.TOMLDecodeError) as error: raise RuntimeError("the canonical package version is unreadable") from error if not isinstance(value, str) or _VERSION.fullmatch(value) is None: @@ -59,16 +63,20 @@ def _current_version(root: Path) -> str: return value -def _json_projection(path: Path, current: str, target: str, field: tuple[str, ...]) -> str: +def _json_projection(path: Path, current: str, target: str, *fields: tuple[str, ...]) -> str: try: payload = json.loads(path.read_text(encoding="utf-8")) - item: object = payload - for key in field[:-1]: - if not isinstance(item, dict): raise RuntimeError(f"invalid object projection {path}") - item = item[key] - if not isinstance(item, dict) or item.get(field[-1]) != current: - raise RuntimeError(f"canonical version projection drift in {path}") - item[field[-1]] = target + if not isinstance(payload, dict): + raise RuntimeError(f"invalid root object projection {path}") + for field in fields: + item: object = payload + for key in field[:-1]: + if not isinstance(item, dict) or key not in item: + raise RuntimeError(f"invalid object projection {path}") + item = item[key] + if not isinstance(item, dict) or not isinstance(item.get(field[-1]), str) or item[field[-1]] != current: + raise RuntimeError(f"canonical version projection drift in {path}") + item[field[-1]] = target return json.dumps(payload, indent=2) + "\n" except (OSError, KeyError, TypeError, json.JSONDecodeError) as error: raise RuntimeError(f"invalid canonical version projection {path}") from error @@ -84,10 +92,27 @@ def _python_projection(path: Path, current: str, target: str) -> str: def _toml_projection(path: Path, current: str, target: str) -> str: text = path.read_text(encoding="utf-8") - old = f'version = "{current}"' - if text.count(old) != 1: + # TOML parsing establishes that this is the product's [project].version. + # The subsequent narrow replacement preserves the surrounding document + # rather than treating an arbitrary dependency/version line as canonical. + try: + document = tomllib.loads(text) + project = document["project"] + except (KeyError, TypeError, tomllib.TOMLDecodeError) as error: + raise RuntimeError(f"invalid canonical package projection {path}") from error + if not isinstance(project, dict) or project.get("version") != current: + raise RuntimeError(f"canonical package version projection drift in {path}") + section = re.compile(r"(?ms)^\[project\]\s*$\n(?P.*?)(?=^\[|\Z)") + match = section.search(text) + if match is None: + raise RuntimeError(f"canonical package version projection drift in {path}") + version = re.compile(rf'(?m)^(?P\s*version\s*=\s*)"{re.escape(current)}"\s*$') + matches = list(version.finditer(match.group("body"))) + if len(matches) != 1: raise RuntimeError(f"canonical package version projection drift in {path}") - return text.replace(old, f'version = "{target}"', 1) + start = match.start("body") + matches[0].start() + end = match.start("body") + matches[0].end() + return text[:start] + f'{matches[0].group("prefix")}"{target}"' + text[end:] def _prepared_writes(root: Path, current: str, target: str) -> dict[Path, str]: @@ -96,7 +121,9 @@ def _prepared_writes(root: Path, current: str, target: str) -> dict[Path, str]: return { paths["pyproject.toml"]: _toml_projection(paths["pyproject.toml"], current, target), paths["package.json"]: _json_projection(paths["package.json"], current, target, ("version",)), - paths["package-lock.json"]: _json_projection(paths["package-lock.json"], current, target, ("version",)), + paths["package-lock.json"]: _json_projection( + paths["package-lock.json"], current, target, ("version",), ("packages", "", "version"), + ), paths["src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json"]: _json_projection(paths["src/engineering_platform/ENGINEERING_PLATFORM_VERSION.json"], current, target, ("platform_version",)), paths["src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json"]: _json_projection(paths["src/engineering_platform/ENGINEERING_PLATFORM_CONFIG.json"], current, target, ("platform", "version")), paths["src/engineering_platform/templates/workspace-config.json"]: _json_projection(paths["src/engineering_platform/templates/workspace-config.json"], current, target, ("platform", "version")), diff --git a/tools/qualification/platform_version_consistency.py b/tools/qualification/platform_version_consistency.py index 9caf49b0..3bf562b8 100644 --- a/tools/qualification/platform_version_consistency.py +++ b/tools/qualification/platform_version_consistency.py @@ -1,62 +1,113 @@ #!/usr/bin/env python3 -"""Fail closed when a public Engineering Platform version projection drifts.""" +"""Fail closed on EP source or installed-version projection drift. + +Source validation is deliberately static. Installed validation is delegated to +an isolated interpreter so a checkout's ``src`` tree cannot masquerade as the +wheel that was just qualified. +""" from __future__ import annotations import argparse -from importlib.metadata import version as installed_version +import ast import json +import os from pathlib import Path -import sys +import subprocess import tomllib +def _object(path: Path) -> dict[str, object]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as error: + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT invalid JSON {path}") from error + if not isinstance(value, dict): + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT invalid JSON root {path}") + return value + + +def _field(document: dict[str, object], path: Path, *keys: str) -> str: + value: object = document + for key in keys: + if not isinstance(value, dict) or key not in value: + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT missing {path}:{'.'.join(keys)}") + value = value[key] + if not isinstance(value, str): + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT invalid type {path}:{'.'.join(keys)}") + return value + + +def _runtime_constant(path: Path) -> str: + try: + tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + except (OSError, SyntaxError) as error: + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT invalid runtime projection {path}") from error + values = [node.value.value for node in tree.body if isinstance(node, ast.Assign) for target in node.targets if isinstance(target, ast.Name) and target.id == "CURRENT_PLATFORM_VERSION" and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)] + if len(values) != 1: + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT invalid runtime constant {path}") + return values[0] + + +def _source_projections(root: Path) -> tuple[str, dict[str, str]]: + try: + package = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) + project = package["project"] + except (OSError, KeyError, TypeError, tomllib.TOMLDecodeError) as error: + raise RuntimeError("EP_VERSION_COMPONENT_DRIFT invalid pyproject.toml") from error + if not isinstance(project, dict) or not isinstance(project.get("version"), str): + raise RuntimeError("EP_VERSION_COMPONENT_DRIFT invalid [project].version") + source = root / "src" / "engineering_platform" + manifest = _object(source / "ENGINEERING_PLATFORM_VERSION.json") + expected = _field(manifest, source / "ENGINEERING_PLATFORM_VERSION.json", "platform_version") + package_lock = _object(root / "package-lock.json") + return expected, { + "package": project["version"], "package_json_root": _field(_object(root / "package.json"), root / "package.json", "version"), + "package_lock_root": _field(package_lock, root / "package-lock.json", "version"), "package_lock_workspace_root": _field(package_lock, root / "package-lock.json", "packages", "", "version"), + "manifest_platform": expected, "manifest_runner": _field(manifest, source / "ENGINEERING_PLATFORM_VERSION.json", "runner_version"), + "manifest_dashboard": _field(manifest, source / "ENGINEERING_PLATFORM_VERSION.json", "dashboard_version"), "manifest_watcher": _field(manifest, source / "ENGINEERING_PLATFORM_VERSION.json", "watcher_version"), + "platform_configuration": _field(_object(source / "ENGINEERING_PLATFORM_CONFIG.json"), source / "ENGINEERING_PLATFORM_CONFIG.json", "platform", "version"), + "workspace_template": _field(_object(source / "templates" / "workspace-config.json"), source / "templates" / "workspace-config.json", "platform", "version"), + "runtime_constant": _runtime_constant(source / "platform_version.py"), + } + + +def _installed_projections(root: Path, python: Path) -> dict[str, str]: + program = '''import importlib.metadata, importlib.resources, json, os, pathlib +import engineering_platform +from engineering_platform import server +from engineering_platform.platform_version import CURRENT_PLATFORM_VERSION, RunnerCompatibility +from engineering_platform.server_console_services import DASHBOARD_VERSION +base = importlib.resources.files("engineering_platform") +manifest = json.loads(base.joinpath("ENGINEERING_PLATFORM_VERSION.json").read_text()) +config = json.loads(base.joinpath("ENGINEERING_PLATFORM_CONFIG.json").read_text()) +template = json.loads(base.joinpath("templates/workspace-config.json").read_text()) +if pathlib.Path(engineering_platform.__file__).resolve().is_relative_to(pathlib.Path(os.environ["EP_SOURCE_ROOT"]).resolve()): raise RuntimeError("installed module was loaded from source checkout") +print(json.dumps({"installed_package": importlib.metadata.version("engineering-platform"), "manifest_platform": manifest["platform_version"], "manifest_runner": manifest["runner_version"], "manifest_dashboard": manifest["dashboard_version"], "manifest_watcher": manifest["watcher_version"], "platform_configuration": config["platform"]["version"], "workspace_template": template["platform"]["version"], "runtime_constant": CURRENT_PLATFORM_VERSION, "runner": RunnerCompatibility().runner_version, "console": DASHBOARD_VERSION, "server": server._console_platform_version()}))''' + environment = os.environ.copy() + environment["EP_SOURCE_ROOT"] = str(root.resolve()) + completed = subprocess.run((str(python), "-I", "-c", program), check=False, capture_output=True, text=True, env=environment) + if completed.returncode: + raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT installed verification failed: {completed.stderr.strip()}") + try: + payload = json.loads(completed.stdout) + except json.JSONDecodeError as error: + raise RuntimeError("EP_VERSION_COMPONENT_DRIFT invalid installed verification result") from error + if not isinstance(payload, dict) or not all(isinstance(value, str) for value in payload.values()): + raise RuntimeError("EP_VERSION_COMPONENT_DRIFT invalid installed projection types") + return payload + + def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument("--source-root", type=Path, default=Path.cwd()) + parser.add_argument("--installed-python", type=Path, help="verify the wheel installed in this isolated interpreter") args = parser.parse_args(argv) - root = args.source_root.resolve() - source = root / "src" - if str(source) not in sys.path: - sys.path.insert(0, str(source)) - - from engineering_platform import server - from engineering_platform.platform_api import PlatformConfiguration - from engineering_platform.platform_version import ( - CURRENT_PLATFORM_VERSION, - EngineeringPlatformManifest, - RunnerCompatibility, - ) - from engineering_platform.server_console_services import DASHBOARD_VERSION - - manifest_path = source / "engineering_platform" / "ENGINEERING_PLATFORM_VERSION.json" - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - expected = manifest["platform_version"] - package = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8")) - package_json = json.loads((root / "package.json").read_text(encoding="utf-8")) - package_lock = json.loads((root / "package-lock.json").read_text(encoding="utf-8")) - configuration = json.loads((source / "engineering_platform" / "ENGINEERING_PLATFORM_CONFIG.json").read_text(encoding="utf-8")) - template = json.loads((source / "engineering_platform" / "templates" / "workspace-config.json").read_text(encoding="utf-8")) - - projections = { - "package": package["project"]["version"], - "package_json_root": package_json["version"], - "package_lock_root": package_lock["version"], - "package_lock_workspace_root": package_lock["packages"][""]["version"], - "installed_package": installed_version("engineering-platform"), - "manifest_platform": manifest["platform_version"], - "manifest_runner": manifest["runner_version"], - "manifest_dashboard": manifest["dashboard_version"], - "manifest_watcher": manifest["watcher_version"], - "platform_configuration": configuration["platform"]["version"], - "workspace_template": template["platform"]["version"], - "runtime_constant": CURRENT_PLATFORM_VERSION, - "runner": RunnerCompatibility().runner_version, - "console": DASHBOARD_VERSION, - "server": server._console_platform_version(), - "platform_api": PlatformConfiguration.load(root).platform.version, - "installed_manifest": EngineeringPlatformManifest.load(manifest_path).platform_version, - } + expected, projections = _source_projections(args.source_root.resolve()) + if args.installed_python is not None: + # Keep a virtualenv launcher path intact: resolving it follows its + # symlink to the base interpreter and silently defeats isolation. + projections.update(_installed_projections(args.source_root.resolve(), args.installed_python)) drift = {name: value for name, value in projections.items() if value != expected} if drift: raise RuntimeError(f"EP_VERSION_COMPONENT_DRIFT expected={expected} observed={drift}") From 7e17851120f552748a16c0735caa0e96fc219374 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 19:00:47 +0200 Subject: [PATCH 83/87] feat: adopt EP bootstrap release cadence v2 --- docs/development/LOCAL_AGENT_RUNNER.md | 17 +++++++---------- .../engineering/test_platform_productization.py | 16 ++++++++++++++++ tools/qualification/advance_platform_build.py | 7 +++++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/docs/development/LOCAL_AGENT_RUNNER.md b/docs/development/LOCAL_AGENT_RUNNER.md index 564e2ecc..eae8ebf8 100644 --- a/docs/development/LOCAL_AGENT_RUNNER.md +++ b/docs/development/LOCAL_AGENT_RUNNER.md @@ -40,16 +40,13 @@ is a separate Engineering Platform release. The private dashboard displays them with the corresponding live components, while its status bar displays the Engineering Platform version and Git commit. -`Canonical versioning` is the only CI writer of the checked-in release -projections. On the first non-bot push to a feature branch it adds one -`build: advance canonical EP patch version X.Y.Z` commit. On a non-bot push to -`main` it adds one `build: advance canonical EP minor version X.Y.0` commit. -The workflow serializes writes per ref, skips `release-*` branches, and uses -`advance_platform_build.py` so the package, manifest, configuration and -workspace-template projections remain identical. A protected branch must -explicitly allow the repository GitHub Actions token to create these bot -commits; otherwise the workflow correctly fails instead of silently claiming a -version bump. +`BOOTSTRAP_RELEASE_CADENCE_V2` (`engineering-platform-bootstrap-release-cadence-v2`) +makes CI read-only. One bounded engineering increment defaults to `PATCH`; +documentation-only work is explicit `NO_BUMP`; `MINOR` is an explicit +capability/release boundary; and `MAJOR`/`EXACT` require applicable authority. +Version preparation happens before final qualification through the protected +delivery seam. Repair, requalification and main merge reuse that operation and +never create a secondary allocation. Existing V1 receipts remain historical. The event policy is shared with Forge and Workspace and is canonically defined by Forge Platform in [Canonical product versioning](https://github.com/pcvantol/forge-platform/blob/main/docs/architecture/CANONICAL_PRODUCT_VERSIONING.md). diff --git a/tests/engineering/test_platform_productization.py b/tests/engineering/test_platform_productization.py index 4bb3604a..bfe98404 100644 --- a/tests/engineering/test_platform_productization.py +++ b/tests/engineering/test_platform_productization.py @@ -180,6 +180,22 @@ def test_canonical_versioning_can_advance_a_minor_and_resets_patch(self) -> None self.assertEqual(module.advance(root, component="minor"), "2.2.0") self.assertEqual(module._current_version(root), "2.2.0") + def test_bootstrap_docs_only_classification_does_not_allocate_a_version(self) -> None: + import importlib.util + script = ROOT / "tools" / "qualification" / "advance_platform_build.py" + spec = importlib.util.spec_from_file_location("advance_platform_build", script) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + for relative in _version_projection_files(): + target = root / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(ROOT / relative, target) + module.set_version(root, "2.3.0") + self.assertEqual(module.advance(root, component="none"), "2.3.0") + def test_release_build_can_set_an_exact_branch_version_across_all_projections(self) -> None: import importlib.util script = ROOT / "tools" / "qualification" / "advance_platform_build.py" diff --git a/tools/qualification/advance_platform_build.py b/tools/qualification/advance_platform_build.py index a6f673f0..11d43eaa 100644 --- a/tools/qualification/advance_platform_build.py +++ b/tools/qualification/advance_platform_build.py @@ -19,6 +19,7 @@ _VERSION = re.compile(r"^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$") +POLICY_REVISION = "engineering-platform-bootstrap-release-cadence-v2" VERSION_PROJECTION_PATHS = ( "pyproject.toml", "package.json", @@ -151,12 +152,14 @@ def advance(root: Path, *, component: str = "patch") -> str: """Advance one stable semantic-version component across all projections.""" current = _current_version(root) major, minor, patch = (int(part) for part in current.split(".")) + if component == "none": + return current if component == "patch": target = f"{major}.{minor}.{patch + 1}" elif component == "minor": target = f"{major}.{minor + 1}.0" else: - raise RuntimeError("version component must be patch or minor") + raise RuntimeError("version component must be none, patch or minor") return set_version(root, target) @@ -164,7 +167,7 @@ def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Apply an explicit canonical EP version operation") parser.add_argument("--source-root", type=Path, default=Path.cwd()) parser.add_argument("--set-version", help="set all canonical projections to this exact stable X.Y.Z version") - parser.add_argument("--bump", choices=("patch", "minor"), help="semantic-version component to advance") + parser.add_argument("--bump", choices=("none", "patch", "minor"), help="bootstrap release classification") parser.add_argument("--expected-version") parser.add_argument("--check", action="store_true") args = parser.parse_args(argv) From e8754a0113e7543fb1c3decfcfd2f2448c0ea6c9 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 19:10:32 +0200 Subject: [PATCH 84/87] fix: preserve installed transport command diagnostics --- tools/qualification/p_transport_installed_ingress_matrix.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/qualification/p_transport_installed_ingress_matrix.py b/tools/qualification/p_transport_installed_ingress_matrix.py index b0649bfb..aa209413 100644 --- a/tools/qualification/p_transport_installed_ingress_matrix.py +++ b/tools/qualification/p_transport_installed_ingress_matrix.py @@ -46,7 +46,10 @@ def command(binary: Path, *args: str, environment: dict[str, str] | None = None) -> dict[str, object]: - completed = subprocess.run([str(binary), *args], check=True, text=True, capture_output=True, env=environment) # nosec B603 + completed = subprocess.run([str(binary), *args], check=False, text=True, capture_output=True, env=environment) # nosec B603 + if completed.returncode: + detail = (completed.stderr or completed.stdout).strip().replace("\n", " ")[:1024] + raise RuntimeError(f"installed Server command failed ({' '.join(args)}): {detail}") return json.loads(completed.stdout) From 7d500a693db966fe496df42764d3693456ae6eb2 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 19:18:40 +0200 Subject: [PATCH 85/87] fix: retry transient installed topology bootstrap --- .../test_transport_authority_guard.py | 25 +++++++++++++++++++ .../p_transport_installed_ingress_matrix.py | 20 ++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/tests/engineering/test_transport_authority_guard.py b/tests/engineering/test_transport_authority_guard.py index 371b84f8..fa9ec5f1 100644 --- a/tests/engineering/test_transport_authority_guard.py +++ b/tests/engineering/test_transport_authority_guard.py @@ -1,8 +1,11 @@ from __future__ import annotations import ast +import importlib.util from pathlib import Path +import subprocess import unittest +from unittest.mock import patch from engineering_platform.platform_components import ( PLATFORM_COMPONENT_IDS, @@ -122,6 +125,28 @@ def test_dependabot_canary_uses_one_bounded_named_wait(self) -> None: self.assertIn("DEPENDABOT_DISPATCH_TIMEOUT_SECONDS = 60", qualification) self.assertIn("timeout=DEPENDABOT_DISPATCH_TIMEOUT_SECONDS", qualification) + def test_installed_bootstrap_retries_only_the_transient_post_crash_store_window(self) -> None: + """Crash recovery may retry idempotent topology bootstrap, never credential issuance.""" + root = Path(__file__).resolve().parents[2] + specification = importlib.util.spec_from_file_location( + "p_transport_installed_ingress_matrix", + root / "tools" / "qualification" / "p_transport_installed_ingress_matrix.py", + ) + self.assertIsNotNone(specification) + module = importlib.util.module_from_spec(specification) # type: ignore[arg-type] + specification.loader.exec_module(module) # type: ignore[union-attr] + unavailable = subprocess.CompletedProcess(("server",), 2, "", '{"error":"EP Server store is unavailable.","ready":false}') + success = subprocess.CompletedProcess(("server",), 0, '{"result":"REGISTERED"}', "") + with patch.object(module.subprocess, "run", side_effect=(unavailable, success)) as invoked, \ + patch.object(module.time, "sleep") as slept: + self.assertEqual(module.command(Path("server"), "bootstrap-topology"), {"result": "REGISTERED"}) + self.assertEqual(invoked.call_count, 2) + slept.assert_called_once_with(module.BOOTSTRAP_TOPOLOGY_RETRY_DELAY_SECONDS) + with patch.object(module.subprocess, "run", return_value=unavailable) as invoked: + with self.assertRaisesRegex(RuntimeError, "issue-consumer-credential"): + module.command(Path("server"), "issue-consumer-credential") + invoked.assert_called_once() + def test_browser_fixture_uses_the_server_boundary_and_no_local_finder_route(self) -> None: """Dashboard browser evidence must not revive the retired direct listener.""" root = Path(__file__).resolve().parents[2] diff --git a/tools/qualification/p_transport_installed_ingress_matrix.py b/tools/qualification/p_transport_installed_ingress_matrix.py index aa209413..67e790de 100644 --- a/tools/qualification/p_transport_installed_ingress_matrix.py +++ b/tools/qualification/p_transport_installed_ingress_matrix.py @@ -44,13 +44,27 @@ # lifecycle semantics depend on runner timing. DEPENDABOT_DISPATCH_TIMEOUT_SECONDS = 60 +# The crash-recovery canaries deliberately terminate the installed Server. +# A hosted SQLite runner can briefly retain the just-released store while the +# next, idempotent topology bootstrap starts. Retry only that bootstrap: an +# issuance or other mutating administrative command must never be replayed +# merely because its response was unavailable. +BOOTSTRAP_TOPOLOGY_RETRY_ATTEMPTS = 4 +BOOTSTRAP_TOPOLOGY_RETRY_DELAY_SECONDS = 0.25 + def command(binary: Path, *args: str, environment: dict[str, str] | None = None) -> dict[str, object]: - completed = subprocess.run([str(binary), *args], check=False, text=True, capture_output=True, env=environment) # nosec B603 - if completed.returncode: + for attempt in range(1, BOOTSTRAP_TOPOLOGY_RETRY_ATTEMPTS + 1): + completed = subprocess.run([str(binary), *args], check=False, text=True, capture_output=True, env=environment) # nosec B603 + if not completed.returncode: + return json.loads(completed.stdout) detail = (completed.stderr or completed.stdout).strip().replace("\n", " ")[:1024] + retryable = args[:1] == ("bootstrap-topology",) and "EP Server store is unavailable." in detail + if retryable and attempt < BOOTSTRAP_TOPOLOGY_RETRY_ATTEMPTS: + time.sleep(BOOTSTRAP_TOPOLOGY_RETRY_DELAY_SECONDS * attempt) + continue raise RuntimeError(f"installed Server command failed ({' '.join(args)}): {detail}") - return json.loads(completed.stdout) + raise AssertionError("bounded installed Server command retry loop exhausted") def wait_for_dispatch( From d310e5a7db0ef1ff989bfb0fcb6e6282138e00e1 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 19:26:16 +0200 Subject: [PATCH 86/87] fix: ignore disconnected read-only health probes --- src/engineering_platform/server.py | 13 +++++++++++-- tests/engineering/test_server_foundation.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/engineering_platform/server.py b/src/engineering_platform/server.py index 338d2bf7..a76e0bd7 100644 --- a/src/engineering_platform/server.py +++ b/src/engineering_platform/server.py @@ -2685,7 +2685,13 @@ def _send(self, status_code: int, payload: dict[str, object], instance_id: str | if route is not None: self.send_header("EP-Console-Route-Owner", route.owner) self.end_headers() - self.wfile.write(encoded) + try: + self.wfile.write(encoded) + except (BrokenPipeError, ConnectionResetError): + # Health probes may abandon a response while the Server finishes + # rendering it. The request has no mutation authority; avoid a + # traceback that obscures qualification diagnostics. + return def _send_ndjson(self, entries: list[dict[str, object]]) -> None: encoded = ("\n".join(json.dumps(entry, sort_keys=True) for entry in entries) + ("\n" if entries else "")).encode("utf-8") @@ -2697,7 +2703,10 @@ def _send_ndjson(self, entries: list[dict[str, object]]) -> None: if route is not None: self.send_header("EP-Console-Route-Owner", route.owner) self.end_headers() - self.wfile.write(encoded) + try: + self.wfile.write(encoded) + except (BrokenPipeError, ConnectionResetError): + return def _send_console_asset(self, request: SplitResult) -> bool: """Serve installed Console assets without selecting a project/root.""" diff --git a/tests/engineering/test_server_foundation.py b/tests/engineering/test_server_foundation.py index 1b03f0ce..0dd63375 100644 --- a/tests/engineering/test_server_foundation.py +++ b/tests/engineering/test_server_foundation.py @@ -58,6 +58,23 @@ def test_queue_disposition_http_helpers_reject_ambiguous_json_and_foreign_origin with self.assertRaises(ValueError): server._strict_json_object(b'[]') + def test_disconnected_read_only_probe_does_not_emit_server_traceback(self) -> None: + """A client closing a health response is not an operational server failure.""" + class DisconnectedWriter: + def write(self, _: bytes) -> None: + raise BrokenPipeError("probe closed") + + class DetachedHandler: + wfile = DisconnectedWriter() + + def send_response(self, _: int) -> None: pass + def send_header(self, _: str, __: str) -> None: pass + def end_headers(self) -> None: pass + + handler = DetachedHandler() + server._HealthHandler._send(handler, 200, {"ready": True}) + server._HealthHandler._send_ndjson(handler, [{"event": "health"}]) + def test_server_presentation_boundaries_reject_unsafe_headers_and_normalize_quota_data(self) -> None: """Console-only helpers remain fail-closed for unsafe or malformed inputs.""" self.assertEqual( From 74f3c1fc989468561f903403dc6b71091ee54447 Mon Sep 17 00:00:00 2001 From: pcvantol Date: Tue, 8 Sep 2026 21:14:16 +0200 Subject: [PATCH 87/87] fix: preserve local genesis assurance target --- src/engineering_platform/execution_host.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engineering_platform/execution_host.py b/src/engineering_platform/execution_host.py index 9a3692ab..de684999 100644 --- a/src/engineering_platform/execution_host.py +++ b/src/engineering_platform/execution_host.py @@ -1901,11 +1901,11 @@ def _inspect_assurance_candidate(self, root: Path, execution_mode: str) -> Repos candidate must therefore be inspectable without Managed's canonical bootstrap document or an ``origin`` remote. """ - try: + if execution_mode != "GENESIS": + return self.repository.inspect(root) + if not (root / ".git").exists(): + # Test/dedicated adapters may provide target evidence directly. return self.repository.inspect(root) - except RunnerError: - if execution_mode != "GENESIS" or not (root / ".git").exists(): - raise provider = getattr(self.repository, "provider", GitProvider()) try: branch = provider.command(root, "git", "branch", "--show-current")