Lean Governance Phase 2A: Decouple Active Control Plane #533

Merged
ahmido merged 1 commits from 471-lean-agent-control-plane-decoupling-v1 into platform-dev 2026-08-11 15:26:04 +00:00
39 changed files with 1849 additions and 3156 deletions

View File

@ -1,11 +1,10 @@
# Mechanical Agent Quality Gates v1
# Lean Agent Quality-Gate Control Plane
Tracked files in this directory are repository-local authorities for Spec 459.
Mutable handoffs, reports, and receipts are local evidence. Receipts belong under
the repository's actual Git metadata directory as resolved by
`git rev-parse --path-format=absolute --git-dir`, specifically
`resolved-git-dir/tenantpilot-agent-gates/receipts/`, so linked worktrees do not write
through a `.git` file path.
Tracked files in this directory provide deterministic risk routing and candidate
safety. The active default is one Main Agent writer, risk-selected test depth,
and one independent final reviewer. FAST and NORMAL do not enter the historical
handoff, Completion Evidence, receipt, correction-contract, calibration, or
profile-qualification lifecycle.
Exit codes:
@ -17,7 +16,7 @@ # Mechanical Agent Quality Gates v1
pull requests, merges, deployments, or remote state changes. Findings redact
secret-like values and report only rule identifiers, paths, and safe line numbers.
Policy-v2 routing is resolved through the existing change-impact command only:
Risk routing is resolved through the existing change-impact command:
```bash
scripts/resolve-change-validation \
@ -27,81 +26,41 @@ # Mechanical Agent Quality Gates v1
--format json
```
`--write-routing-manifest` requests atomic, content-addressed storage below the
resolved Git common directory. The JSON result selects the persisted manifest by
`routing_manifest_path` and `routing_manifest_sha256`. Executable gates always
use `command_authority=REGISTRY`; reviewer roles are selected separately by the
manifest review topology and are never an alternative command authority.
Executable gates use `command_authority=REGISTRY`; reviewer topology is separate
and never supplies commands. Unknown, contradictory, conditional, or incomplete
signals resolve to `BLOCKED` rather than a Candidate.
The frozen policy-v2 manifest assigns every registered gate exactly one
`REQUIRED` or `NOT_REQUIRED` decision. `CONDITIONAL` is resolution-only and must
not survive freeze. Run only `REQUIRED` commands, using the exact command from
`validation-command-registry.json`; retain `NOT_REQUIRED` solely as candidate-
bound router evidence with its controlled reason. It is neither `SKIPPED` nor
`PASS`, and an earlier executed failure is never relabeled. Candidate, completion,
and receipt processing all consume the same persisted manifest and fail closed on
scope, manifest, full-delta, policy, registry, execution-contract, validation, or
review drift.
Every frozen decision is `REQUIRED` or `NOT_REQUIRED`; `CONDITIONAL` never
survives freeze. Run only `REQUIRED` Registry commands. `NOT_REQUIRED` is neither
`SKIPPED` nor `PASS`, and an executed failure is never relabelled.
The same Registry owns the closed execution-phase catalog. The manifest freezes
`PRE_CANDIDATE` or `POST_CANDIDATE_VALIDATION` beside each Gate Requirement. A
Candidate report must contain actual results for every required Pre-Candidate gate
and no result for a Post-Candidate gate. After Candidate, the Validation Handoff
supplies every routed validation result with Registry command identity plus
Candidate/manifest/report binding. Completion consumes that handoff and the clean
Review Result, verifies the report is byte-identical, and never rewrites it.
FAST uses focused proof. NORMAL normally adds Fast Feedback. CRITICAL may add
signal-selected deep tests and at most one independent validator. All three keep
one Main-Agent writer and exactly one final reviewer.
Baseline attribution uses `baseline-comparison.schema.json` and remains separate
from routing. `scripts/run-agent-quality-gates baseline-comparison` consumes a
frozen routing manifest plus complete Candidate/baseline lane bundles and derives
three dimensions: Environment, Actual Command, and Test Set. Selection Identity
belongs to Test Set; Registry Definition Identity is distinct from Actual
Invocation Identity. Test-set relation is derived as `EXACT` or, only with frozen
governance Risk Signals, `MONOTONE_SUPERSET`. The command has no manual relation or
identity override. Any dimension mismatch is `NOT_COMPARABLE`; success permits
only `PASS_WITH_ATTRIBUTED_BASELINE` while the executed lane remains red/blocking.
Spec-package readiness is risk based:
For the Spec 468 routing-authority implementation itself, the narrow complete
proof is repository-governance shell coverage, the foundation validator, one
focused Pest guard, and Sail Pint. Browser and PostgreSQL lanes are not applicable
because no rendered UI, browser workflow, migration, database contract, or
PostgreSQL-specific behavior changes. Deployment is not applicable because the
candidate changes only repository-local governance contracts/tooling: no runtime,
environment, queue, scheduler, storage, asset, container, Dokploy, staging, or
production state changes. No dedicated follow-up spec is required because the
routing/evidence cutover is one bounded unit; parked dependent work remains
outside this feature and resumes only after integration.
- FAST/NORMAL: `spec.md`, `plan.md`, `tasks.md`, requirements checklist, clear
scope and acceptance criteria, test strategy, and final review.
- CRITICAL: the same package plus a compact `execution-contract.json` when the
risk/remote boundary requires it.
- Packages without an explicit current Lean risk declaration fail closed. The
validator has no historical loader or legacy fallback.
Normal active specs use their own `execution-contract.json` via explicit
`--spec`. Completed specs are audit-only unless a local additive correction
contract is supplied. Current correction authority is a separate attributable
precondition and evidence decision; it is not a CLI credential, `--authority-id`,
or contract field. The canonical issuer invocation is:
The Lean local closeout is: required checks, one helper-compatible Candidate
fingerprint, one final reviewer verdict, and `scripts/codex-safe-local-commit`
Lean mode with expected HEAD, explicit paths, Candidate SHA-256, empty index, no
unmerged paths, explicit current commit authority, and
`NO_CONFIRMED_FINDINGS`.
```bash
scripts/issue-additive-correction-contract \
--spec specs/<historical-target> \
--session-branch <exact-session-branch> \
--start-head <exact-head> \
--integration-base <base> \
--integration-target <target> \
--diff-baseline <baseline> \
--summary '<bounded correction summary>' \
--root-cause '<confirmed root cause>' \
--path <exact-repository-relative-path>
```
The repository may temporarily contain obsolete receipt, handoff, completion,
correction, baseline, profile, and calibration files. They are unsupported dead
code, not compatibility. They have no active Control-Plane consumer and are
assigned to physical deletion in Spec 472 or 473. Git history is the historical
archive.
`--spec` is the historical target; `--target-spec`, `--authority-id`, caller-
supplied target metadata, and governance-feature branch inference are rejected.
The canonical `ADDITIVE_CORRECTION` / `additive-post-integration` contract lives
under the current worktree's resolved Git metadata directory at
`tenantpilot-agent-gates/corrections/`. Its SHA-256 filename identifies its exact
canonical bytes. It binds one derived historical target, exact session/start/
base/target/baseline values, explicit correction context, and one exact path set.
It is local-only evidence and is never correction authority, commit authority, or
remote authorization.
`ACTIVE_CONTROL_PLANE = LEAN_ONLY`; no compatibility flag, dual dispatcher,
legacy runtime, receipt mode, or historical execution mode is supported.
Receipts remain separate stale-evidence protection under
`tenantpilot-agent-gates/receipts/`. `scripts/codex-safe-local-commit` still
requires matching contract/receipt/candidate/paths, helper fingerprint, and
separate current user commit authority before a local commit.
No quality-gate command grants commit, push, PR, merge, deployment, or remote
authority. Every remote mutation remains a separate explicit authorization.

View File

@ -13,7 +13,16 @@
"classification_order": ["CRITICAL", "FAST", "NORMAL", "BLOCKED"],
"requirement_states": ["REQUIRED", "NOT_REQUIRED", "CONDITIONAL"],
"frozen_requirement_states": ["REQUIRED", "NOT_REQUIRED"],
"known_roles": ["code_explorer", "implementer", "test_validator", "code_reviewer", "git_finalizer"],
"known_roles": ["code_explorer", "test_validator", "code_reviewer"],
"retired_gate_ids": [
"additive-correction-contract",
"agent-handoff",
"fast-feedback-local-calibration",
"fast-feedback-profile-contract",
"fast-feedback-qualified-enforcement",
"finalization-receipt",
"implementation-report"
],
"reason_codes": {
"CORE_GATE_REQUIRED": {
"requirement": "REQUIRED",
@ -27,34 +36,14 @@
},
"RISK_CLASS_GATE_REQUIRED": {
"requirement": "REQUIRED",
"gate_ids": ["focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "foundation-regression", "agent-handoff", "implementation-report", "finalization-receipt"],
"gate_ids": ["focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "foundation-regression"],
"consequence": "Execute proof imposed by the resolved risk class."
},
"IMPACT_SIGNAL_GATE_REQUIRED": {
"requirement": "REQUIRED",
"gate_ids": ["browser", "postgresql-discovery", "postgresql-lane", "ui-coverage-guard", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback-qualified-enforcement", "profiling", "composer-validate", "composer-audit", "pnpm-audit", "platform-build", "website-build", "website-smoke", "website-test", "full-platform-suite", "platform-pint", "quality-gate-self-tests", "foundation-regression", "additive-correction-contract", "agent-handoff", "implementation-report", "finalization-receipt"],
"gate_ids": ["browser", "postgresql-discovery", "postgresql-lane", "ui-coverage-guard", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "profiling", "composer-validate", "composer-audit", "pnpm-audit", "platform-build", "website-build", "website-smoke", "website-test", "full-platform-suite", "platform-pint", "quality-gate-self-tests", "foundation-regression"],
"consequence": "Execute specialized proof selected by a controlled impact signal."
},
"LIFECYCLE_EVIDENCE_REQUIRED": {
"requirement": "REQUIRED",
"gate_ids": ["agent-handoff", "implementation-report", "finalization-receipt", "independent-review"],
"consequence": "Preserve the candidate validation, review, completion, and receipt chain."
},
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_HANDOFF": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["agent-handoff"],
"consequence": "Keep handoff gates unexecuted for the Lean FAST/NORMAL workflow."
},
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_COMPLETION_EVIDENCE": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["implementation-report"],
"consequence": "Keep completion-evidence validation outside the Lean FAST/NORMAL workflow."
},
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_RECEIPT": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["finalization-receipt"],
"consequence": "Keep receipt issuance and consumption outside the Lean FAST/NORMAL workflow."
},
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_CONFIDENCE": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["confidence"],
@ -100,16 +89,6 @@
"gate_ids": ["quality-gate-self-tests", "foundation-regression"],
"consequence": "Keep governance regression proof unexecuted when governance authority is untouched."
},
"NO_TEST_PROFILE_GOVERNANCE_IMPACT": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback-qualified-enforcement"],
"consequence": "Keep profile and budget proof unexecuted when profile authority is untouched."
},
"NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["fast-feedback-local-calibration"],
"consequence": "Keep local profile qualification unexecuted when a mechanically verified lane-evidence change does not alter budget, profile, or calibration semantics."
},
"NO_HEAVY_GOVERNANCE_SIGNAL": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["heavy-governance"],
@ -125,11 +104,6 @@
"gate_ids": ["full-platform-suite"],
"consequence": "Keep the full suite unexecuted when no whole-platform trigger exists."
},
"NO_CORRECTION_CONTEXT": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["additive-correction-contract"],
"consequence": "Keep correction-contract validation unexecuted outside an authorized correction session."
},
"NO_PLATFORM_TEST_SCOPE": {
"requirement": "NOT_REQUIRED",
"gate_ids": ["focused-platform-pest", "fast-feedback"],
@ -163,14 +137,13 @@
"browser-product-surface": {"risk_floor": "CRITICAL", "required_gates": ["ui-coverage-guard", "browser", "platform-build"], "validator_roles": ["test_validator"], "impact_flags": ["product_surface"]},
"delivery-remote-git": {"risk_floor": "CRITICAL", "required_gates": ["website-build", "website-smoke", "website-test"], "validator_roles": ["test_validator"], "impact_flags": ["delivery_remote"]},
"security": {"risk_floor": "CRITICAL", "required_gates": ["focused-platform-pest", "fast-feedback", "confidence"], "validator_roles": ["test_validator"], "impact_flags": ["security"]},
"quality-gate-authority": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "foundation-regression", "fast-feedback", "confidence", "heavy-governance"], "validator_roles": ["test_validator"], "impact_flags": []},
"test-selection": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "fast-feedback-profile-contract", "fast-feedback", "confidence", "heavy-governance"], "validator_roles": ["test_validator"], "impact_flags": []},
"quality-gate-authority": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "foundation-regression", "fast-feedback"], "validator_roles": ["test_validator"], "impact_flags": []},
"test-selection": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "fast-feedback", "confidence", "heavy-governance"], "validator_roles": ["test_validator"], "impact_flags": []},
"lane-evidence-governance": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "foundation-regression", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance"], "validator_roles": ["test_validator"], "impact_flags": []},
"budget-governance": {"risk_floor": "CRITICAL", "required_gates": ["fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "profiling"], "validator_roles": ["test_validator"], "impact_flags": []},
"receipt-finalization-authority": {"risk_floor": "CRITICAL", "required_gates": ["agent-handoff", "implementation-report", "finalization-receipt", "quality-gate-self-tests", "foundation-regression", "confidence", "heavy-governance"], "validator_roles": ["test_validator"], "impact_flags": []},
"budget-governance": {"risk_floor": "CRITICAL", "required_gates": ["confidence", "heavy-governance", "profiling"], "validator_roles": ["test_validator"], "impact_flags": []},
"local-finalization-authority": {"risk_floor": "CRITICAL", "required_gates": ["quality-gate-self-tests", "foundation-regression"], "validator_roles": ["test_validator"], "impact_flags": []},
"dependency-manifest": {"risk_floor": "CRITICAL", "required_gates": ["composer-validate", "composer-audit", "pnpm-audit"], "validator_roles": ["test_validator"], "impact_flags": ["security"]},
"build-impact": {"risk_floor": "NORMAL", "required_gates": ["platform-build"], "validator_roles": [], "impact_flags": []},
"correction-context": {"risk_floor": "CRITICAL", "required_gates": ["additive-correction-contract", "finalization-receipt"], "validator_roles": ["test_validator"], "impact_flags": []}
"build-impact": {"risk_floor": "NORMAL", "required_gates": ["platform-build"], "validator_roles": [], "impact_flags": []}
},
"gate_policy": {
"spec-package": {"default_requirement": "REQUIRED", "default_reason_code": "CORE_GATE_REQUIRED"},
@ -179,18 +152,11 @@
"unicode": {"default_requirement": "REQUIRED", "default_reason_code": "CORE_GATE_REQUIRED"},
"postgresql-discovery": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_DATABASE_IMPACT"},
"change-validation": {"default_requirement": "REQUIRED", "default_reason_code": "CORE_GATE_REQUIRED"},
"agent-handoff": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "LEAN_WORKFLOW_DOES_NOT_REQUIRE_HANDOFF"},
"implementation-report": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "LEAN_WORKFLOW_DOES_NOT_REQUIRE_COMPLETION_EVIDENCE"},
"finalization-receipt": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "LEAN_WORKFLOW_DOES_NOT_REQUIRE_RECEIPT"},
"additive-correction-contract": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_CORRECTION_CONTEXT"},
"foundation-regression": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_GOVERNANCE_AUTHORITY_IMPACT"},
"quality-gate-self-tests": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_GOVERNANCE_AUTHORITY_IMPACT"},
"git-diff-check": {"default_requirement": "REQUIRED", "default_reason_code": "CORE_GATE_REQUIRED"},
"ui-coverage-guard": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_PRODUCT_SURFACE_IMPACT"},
"focused-platform-pest": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_PLATFORM_TEST_SCOPE"},
"fast-feedback-profile-contract": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_TEST_PROFILE_GOVERNANCE_IMPACT"},
"fast-feedback-local-calibration": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_TEST_PROFILE_GOVERNANCE_IMPACT"},
"fast-feedback-qualified-enforcement": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_TEST_PROFILE_GOVERNANCE_IMPACT"},
"fast-feedback": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_PLATFORM_TEST_SCOPE"},
"confidence": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "LEAN_WORKFLOW_DOES_NOT_REQUIRE_CONFIDENCE"},
"browser": {"default_requirement": "NOT_REQUIRED", "default_reason_code": "NO_PRODUCT_SURFACE_IMPACT"},
@ -236,14 +202,15 @@
"required_review_roles": ["code_reviewer"]
},
"critical_policy": {
"signal_ids": ["authentication", "authorization", "tenant-workspace-scope", "provider-runtime", "provider-credentials", "provider-permissions", "database", "migration", "queue-concurrency", "destructive-action", "backup-restore", "customer-output", "browser-product-surface", "delivery-remote-git", "security", "quality-gate-authority", "test-selection", "lane-evidence-governance", "budget-governance", "receipt-finalization-authority", "dependency-manifest", "correction-context"],
"signal_ids": ["authentication", "authorization", "tenant-workspace-scope", "provider-runtime", "provider-credentials", "provider-permissions", "database", "migration", "queue-concurrency", "destructive-action", "backup-restore", "customer-output", "browser-product-surface", "delivery-remote-git", "security", "quality-gate-authority", "test-selection", "lane-evidence-governance", "budget-governance", "local-finalization-authority", "dependency-manifest"],
"required_review_roles": ["test_validator", "code_reviewer"]
},
"bootstrap_topology": {
"required_roles": ["code_explorer", "implementer", "test_validator", "code_reviewer"],
"sequential": true,
"no_parallel_writers": true,
"git_finalizer_requires_separate_authority": true
"active_topology": {
"main_agent_writers": 1,
"optional_roles": ["code_explorer"],
"critical_validator_max": 1,
"final_reviewer_count": 1,
"no_parallel_writers": true
},
"diff_classification_contracts": {
"lane-evidence-vs-budget-profile-calibration-v1": {
@ -301,21 +268,13 @@
}
},
"rules": [
{
"prefix": "specs/464-platform-fast-feedback-profile-separation-budget-governance-v1/",
"classification": "fast-feedback-governance-evidence",
"gates": ["spec-package", "diff-scope", "diff-safety", "unicode", "change-validation", "implementation-report", "fast-feedback-profile-contract", "fast-feedback-local-calibration"],
"skills": ["spec-readiness-gate", "pest-testing"],
"risks": ["fast-feedback-profile-contract", "calibration-evidence", "historical-evidence-integrity", "no-remote"],
"signal_ids": ["budget-governance"]
},
{
"id": "active-spec-path",
"prefix": "specs/",
"classification": "active-spec",
"gates": ["spec-package", "diff-scope", "diff-safety", "unicode", "change-validation", "implementation-report"],
"gates": ["spec-package", "diff-scope", "diff-safety", "unicode", "change-validation"],
"skills": ["spec-readiness-gate"],
"risks": ["active-spec", "report-truth", "execution-contract"],
"risks": ["active-spec"],
"signal_ids": ["docs-only"]
},
{
@ -329,9 +288,9 @@
{
"prefix": ".agent/quality-gates/",
"classification": "quality-gate-governance",
"gates": ["spec-package", "diff-scope", "diff-safety", "unicode", "change-validation", "quality-gate-self-tests", "agent-handoff", "implementation-report", "finalization-receipt"],
"gates": ["spec-package", "diff-scope", "diff-safety", "unicode", "change-validation", "quality-gate-self-tests", "foundation-regression"],
"skills": ["spec-readiness-gate"],
"risks": ["repository-governance", "agent-contract", "receipt-authority", "determinism"],
"risks": ["repository-governance", "agent-contract", "determinism"],
"signal_ids": ["quality-gate-authority"]
},
{
@ -358,19 +317,10 @@
"risks": ["repository-tooling", "local-finalization", "no-remote"],
"signal_ids": ["quality-gate-authority"]
},
{
"id": "receipt-authority-path",
"path": "scripts/issue-agent-finalization-receipt",
"classification": "receipt-finalization-authority",
"gates": ["agent-handoff", "implementation-report", "finalization-receipt", "quality-gate-self-tests", "foundation-regression", "confidence", "heavy-governance"],
"skills": ["spec-readiness-gate"],
"risks": ["receipt-authority", "local-finalization"],
"signal_ids": ["receipt-finalization-authority"]
},
{
"path": "scripts/platform-test-lane",
"classification": "test-lane-runner",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-discovery", "postgresql-lane", "profiling"],
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "fast-feedback", "confidence", "heavy-governance", "postgresql-discovery", "postgresql-lane", "profiling"],
"skills": ["pest-testing"],
"risks": ["test-lane-routing", "shared-test-infrastructure", "execution-profile", "hybrid-budget", "postgresql"],
"signal_ids": ["test-selection", "budget-governance"]
@ -378,7 +328,7 @@
{
"path": "scripts/platform-test-report",
"classification": "test-lane-report",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-discovery", "postgresql-lane", "profiling"],
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "confidence", "heavy-governance", "postgresql-discovery", "postgresql-lane", "profiling"],
"skills": ["pest-testing"],
"risks": ["test-lane-reporting", "execution-profile", "hybrid-budget", "artifact-contract", "historical-evidence-integrity"],
"signal_ids": ["budget-governance"]
@ -386,10 +336,10 @@
{
"path": "scripts/codex-safe-local-commit",
"classification": "safe-local-finalization-helper",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression", "finalization-receipt"],
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression", "quality-gate-self-tests", "confidence", "heavy-governance"],
"skills": ["spec-readiness-gate"],
"risks": ["local-finalization", "commit-authority", "receipt-authority", "no-remote"],
"signal_ids": ["receipt-finalization-authority"]
"risks": ["local-finalization", "commit-authority", "no-remote"],
"signal_ids": ["local-finalization-authority"]
},
{
"id": "platform-test-path",
@ -411,7 +361,7 @@
{
"path": "apps/platform/tests/Support/TestLaneManifest.php",
"classification": "test-lane-manifest",
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"skills": ["pest-testing"],
"risks": ["test-lane-routing", "shared-test-infrastructure", "postgresql", "budget-trends"],
"signal_ids": ["test-selection", "lane-evidence-governance", "budget-governance"],
@ -420,7 +370,7 @@
{
"path": "apps/platform/tests/Support/TestLaneBudget.php",
"classification": "test-lane-budget",
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"skills": ["pest-testing"],
"risks": ["shared-test-infrastructure", "execution-profile", "hybrid-budget", "budget-trends"],
"signal_ids": ["budget-governance"]
@ -428,7 +378,7 @@
{
"path": "apps/platform/tests/Support/TestLaneReport.php",
"classification": "test-lane-report-support",
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"gates": ["diff-scope", "diff-safety", "unicode", "postgresql-discovery", "change-validation", "focused-platform-pest", "fast-feedback", "confidence", "heavy-governance", "postgresql-lane", "profiling"],
"skills": ["pest-testing"],
"risks": ["shared-test-infrastructure", "artifact-contract", "historical-evidence-integrity", "budget-trends"],
"signal_ids": ["lane-evidence-governance", "budget-governance"],
@ -490,12 +440,20 @@
"risks": ["spec-kit-extension-authority"],
"signal_ids": ["quality-gate-authority"]
},
{
"path": ".codex/config.toml",
"classification": "agent-runtime-configuration",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression", "quality-gate-self-tests"],
"skills": ["spec-readiness-gate"],
"risks": ["agent-contract", "repository-governance"],
"signal_ids": ["quality-gate-authority"]
},
{
"prefix": ".codex/agents/",
"classification": "agent-role-contract",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression", "agent-handoff"],
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression"],
"skills": ["spec-readiness-gate"],
"risks": ["agent-contract", "handoff-provenance"],
"risks": ["agent-contract"],
"signal_ids": ["quality-gate-authority"]
},
{
@ -519,9 +477,9 @@
{
"path": "AGENTS.md",
"classification": "root-agent-instructions",
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression", "agent-handoff"],
"gates": ["diff-scope", "diff-safety", "unicode", "change-validation", "foundation-regression"],
"skills": ["spec-readiness-gate"],
"risks": ["instruction-contract", "handoff-provenance"],
"risks": ["instruction-contract"],
"signal_ids": ["quality-gate-authority"]
},
{

View File

@ -51,13 +51,7 @@ public function run(array $argv): int
'check-unicode-mojibake' => $this->checkUnicode($args),
'discover-postgresql-tests' => $this->discoverPostgresql($args),
'resolve-change-validation' => $this->resolveChangeValidation($args),
'validate-agent-handoff' => $this->validateAgentHandoff($args),
'validate-implementation-report' => $this->validateImplementationReport($args),
'validate-json-artifact' => $this->validateJsonArtifact($args),
'run-agent-quality-gates' => $this->runQualityGates($args),
'issue-additive-correction-contract' => $this->issueCorrectionContract($args),
'validate-additive-correction-contract' => $this->validateCorrectionContractCommand($args),
'issue-agent-finalization-receipt' => $this->issueReceipt($args),
default => $this->fail('unknown-command', 2),
};
}
@ -765,17 +759,14 @@ private function registryCommandIdentity(string $gateId, array $registry): ?stri
}
/** @return array{violations:list<string>,payload:array<string,mixed>} */
private function validationPlan(string $base, ?string $spec = null, ?string $correctionContract = null): array
private function validationPlan(string $base, ?string $spec = null): array
{
$registry = $this->readJson('.agent/quality-gates/validation-command-registry.json');
$rules = $this->routingPolicy($registry);
$contract = ['declared_na_gates' => [], 'required_gates' => []];
if ($spec !== null && $spec !== '') {
if ($spec !== null && $spec !== '' && is_file(rtrim($spec, '/').'/execution-contract.json')) {
$contract = $this->readJson(rtrim($spec, '/').'/execution-contract.json');
}
$correction = $correctionContract !== null && $correctionContract !== ''
? $this->validatedCorrectionContract($correctionContract, $spec, 'candidate')
: null;
$gates = [];
$skills = [];
$risks = [];
@ -850,6 +841,10 @@ private function validationPlan(string $base, ?string $spec = null, ?string $cor
}
foreach (($contract['required_gates'] ?? []) as $gate) {
$gate = (string) $gate;
if (in_array($gate, $rules['retired_gate_ids'] ?? [], true)) {
$violations[] = "retired-control-plane-gate-forbidden:{$gate}";
continue;
}
if (isset($manualGates[$gate])) {
if ($gate !== 'independent-review') {
$violations[] = "contradiction-required-and-na:{$gate}";
@ -858,9 +853,6 @@ private function validationPlan(string $base, ?string $spec = null, ?string $cor
}
$gates[$gate] = true;
}
if (is_array($correction)) {
$gates['additive-correction-contract'] = true;
}
foreach (array_keys($gates) as $gate) {
if (! isset($registry['commands'][$gate])) {
$violations[] = "unregistered-command:{$gate}";
@ -893,12 +885,12 @@ private function validationPlan(string $base, ?string $spec = null, ?string $cor
ksort($pathClassifications);
$routingScope = $this->routingScope($base, $spec, $rules, $registry);
$violations = array_merge($violations, $routingScope['violations']);
$riskResolution = $this->resolveRiskClassification($base, $routingScope, $rules, $contract, is_array($correction));
$riskResolution = $this->resolveRiskClassification($base, $routingScope, $rules, $contract);
if ($violations !== []) {
$riskResolution = $this->blockedRiskResolution($riskResolution, $violations);
}
$violations = array_merge($violations, $riskResolution['violations']);
$gateResolution = $this->resolveGateRequirements($riskResolution, $contract, $rules, $registry, is_array($correction));
$gateResolution = $this->resolveGateRequirements($riskResolution, $contract, $rules, $registry);
$violations = array_merge($violations, $gateResolution['violations']);
if (($riskResolution['resolver_state'] ?? null) === 'RESOLVED') {
$requirementsByGate = array_column($gateResolution['gate_requirements'], null, 'gate_id');
@ -960,9 +952,6 @@ private function validationPlan(string $base, ?string $spec = null, ?string $cor
'active_signals' => $riskResolution['active_signals'],
'gate_requirements' => $gateResolution['gate_requirements'],
'review_topology' => $reviewTopology['review_topology'],
'correction_contract' => is_array($correction)
? $this->correctionSummary($correction, (string) $correctionContract)
: null,
];
if (($riskResolution['resolver_state'] ?? null) === 'RESOLVED') {
$payload['risk_class'] = $riskResolution['risk_class'];
@ -1024,7 +1013,7 @@ private function routingPolicy(array $registry): array
'resolver_terminal_states' => ['RESOLVED', 'BLOCKED'],
'requirement_states' => ['REQUIRED', 'NOT_REQUIRED', 'CONDITIONAL'],
'frozen_requirement_states' => ['REQUIRED', 'NOT_REQUIRED'],
'known_roles' => ['code_explorer', 'implementer', 'test_validator', 'code_reviewer', 'git_finalizer'],
'known_roles' => ['code_explorer', 'test_validator', 'code_reviewer'],
] as $field => $expected) {
$actual = $policy[$field] ?? null;
if (! is_array($actual) || $actual !== $expected || count($actual) !== count(array_unique($actual))) {
@ -1033,15 +1022,30 @@ private function routingPolicy(array $registry): array
}
$knownRoles = $policy['known_roles'] ?? [];
$retiredGateIds = $policy['retired_gate_ids'] ?? null;
$expectedRetiredGateIds = [
'additive-correction-contract',
'agent-handoff',
'fast-feedback-local-calibration',
'fast-feedback-profile-contract',
'fast-feedback-qualified-enforcement',
'finalization-receipt',
'implementation-report',
];
if ($retiredGateIds !== $expectedRetiredGateIds) {
$violations[] = 'retired-gate-catalog-drift';
$retiredGateIds = $expectedRetiredGateIds;
}
$commandIds = array_keys(is_array($registry['commands'] ?? null) ? $registry['commands'] : []);
sort($commandIds, SORT_STRING);
$activeCommandIds = array_values(array_diff($commandIds, $retiredGateIds));
$gatePolicyIds = array_keys(is_array($policy['gate_policy'] ?? null) ? $policy['gate_policy'] : []);
sort($gatePolicyIds, SORT_STRING);
if ($commandIds !== $gatePolicyIds) {
foreach (array_diff($commandIds, $gatePolicyIds) as $gateId) {
if ($activeCommandIds !== $gatePolicyIds) {
foreach (array_diff($activeCommandIds, $gatePolicyIds) as $gateId) {
$violations[] = "unmapped-registry-gate:{$gateId}";
}
foreach (array_diff($gatePolicyIds, $commandIds) as $gateId) {
foreach (array_diff($gatePolicyIds, $activeCommandIds) as $gateId) {
$violations[] = "unknown-policy-gate:{$gateId}";
}
}
@ -1051,10 +1055,6 @@ private function routingPolicy(array $registry): array
'EXECUTION_CONTRACT_FLOOR',
'RISK_CLASS_GATE_REQUIRED',
'IMPACT_SIGNAL_GATE_REQUIRED',
'LIFECYCLE_EVIDENCE_REQUIRED',
'LEAN_WORKFLOW_DOES_NOT_REQUIRE_HANDOFF',
'LEAN_WORKFLOW_DOES_NOT_REQUIRE_COMPLETION_EVIDENCE',
'LEAN_WORKFLOW_DOES_NOT_REQUIRE_RECEIPT',
'LEAN_WORKFLOW_DOES_NOT_REQUIRE_CONFIDENCE',
'FAST_CONFIDENCE_RELIEF',
'NO_PRODUCT_SURFACE_IMPACT',
@ -1064,12 +1064,9 @@ private function routingPolicy(array $registry): array
'NO_DEPENDENCY_MANIFEST_IMPACT',
'NO_BUILD_IMPACT',
'NO_GOVERNANCE_AUTHORITY_IMPACT',
'NO_TEST_PROFILE_GOVERNANCE_IMPACT',
'NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED',
'NO_HEAVY_GOVERNANCE_SIGNAL',
'NO_PROFILING_SIGNAL',
'NO_FULL_SUITE_TRIGGER',
'NO_CORRECTION_CONTEXT',
'NO_PLATFORM_TEST_SCOPE',
'NO_PLATFORM_FORMATTING_SCOPE',
];
@ -1238,12 +1235,14 @@ private function routingPolicy(array $registry): array
}
}
}
$bootstrapRoles = $policy['bootstrap_topology']['required_roles'] ?? null;
if ($bootstrapRoles !== ['code_explorer', 'implementer', 'test_validator', 'code_reviewer']
|| ($policy['bootstrap_topology']['sequential'] ?? null) !== true
|| ($policy['bootstrap_topology']['no_parallel_writers'] ?? null) !== true
|| ($policy['bootstrap_topology']['git_finalizer_requires_separate_authority'] ?? null) !== true) {
$violations[] = 'invalid-bootstrap-topology';
$activeTopology = $policy['active_topology'] ?? null;
if (! is_array($activeTopology)
|| ($activeTopology['main_agent_writers'] ?? null) !== 1
|| ($activeTopology['optional_roles'] ?? null) !== ['code_explorer']
|| ($activeTopology['critical_validator_max'] ?? null) !== 1
|| ($activeTopology['final_reviewer_count'] ?? null) !== 1
|| ($activeTopology['no_parallel_writers'] ?? null) !== true) {
$violations[] = 'invalid-active-topology';
}
$policy['_routing_policy_violations'] = array_values(array_unique($violations));
@ -1280,7 +1279,7 @@ private function environmentComparisonRegistryViolations(array $registry): array
'test-selection',
'lane-evidence-governance',
'budget-governance',
'receipt-finalization-authority',
'local-finalization-authority',
]) {
$violations[] = 'test-set-comparison-policy-invalid';
}
@ -1888,7 +1887,9 @@ private function routingScope(string $base, ?string $spec, array $policy, array
$derivedEvidencePaths = array_values(array_unique($derivedEvidencePaths));
sort($derivedEvidencePaths, SORT_STRING);
if ($executionContractProjection === null && $spec !== null) {
if ($executionContractProjection === null
&& $spec !== null
&& is_file(rtrim($spec, '/').'/execution-contract.json')) {
try {
$executionContractProjection = $this->executionContractRoutingProjection(
(string) file_get_contents(rtrim($spec, '/').'/execution-contract.json'),
@ -1940,7 +1941,6 @@ private function resolveRiskClassification(
array $routingScope,
array $policy,
array $executionContract,
bool $correctionContext = false,
): array
{
$signalEvidence = [];
@ -1977,10 +1977,6 @@ private function resolveRiskClassification(
}
}
if ($correctionContext) {
$signalEvidence['correction-context']['correction-contract'][] = 'authorized-correction-context';
}
if ($this->isFixtureOnlyCandidate($base, $routingScope, $policy)) {
foreach ($routingScope['entries'] ?? [] as $entry) {
if (is_array($entry) && ($entry['record_type'] ?? null) === 'INPUT') {
@ -2112,7 +2108,7 @@ private function blockedRiskResolution(array $riskResolution, array $violations)
str_contains($violation, 'policy-registry-version-drift') => 'missing-routing-authority',
str_contains($violation, 'routing-candidate-empty') => 'invalid-or-incomplete-candidate',
str_contains($violation, 'manual-risk-downgrade-forbidden') => 'manual-risk-downgrade-forbidden',
str_contains($violation, 'lean-workflow-lifecycle-gate-forbidden') => 'contradictory-routing-input',
str_contains($violation, 'retired-control-plane-gate-forbidden') => 'contradictory-routing-input',
str_contains($violation, 'unclassified:'),
str_contains($violation, 'routing-rule-missing:') => 'unknown-routing-signal',
default => 'invalid-or-incomplete-candidate',
@ -2140,7 +2136,7 @@ private function blockedRiskResolution(array $riskResolution, array $violations)
* @param array<string, mixed> $registry
* @return array{violations:list<string>,gate_requirements:list<array<string,mixed>>}
*/
private function resolveGateRequirements(array $riskResolution, array $contract, array $policy, array $registry, bool $correctionContext): array
private function resolveGateRequirements(array $riskResolution, array $contract, array $policy, array $registry): array
{
if (($riskResolution['resolver_state'] ?? null) !== 'RESOLVED') {
return ['violations' => [], 'gate_requirements' => []];
@ -2158,34 +2154,22 @@ private function resolveGateRequirements(array $riskResolution, array $contract,
$requiredBySignal[(string) $gateId][] = (string) $signalId;
}
}
if ($correctionContext) {
$requiredBySignal['additive-correction-contract'][] = 'correction-context';
}
$riskClass = (string) ($riskResolution['risk_class'] ?? '');
$requiredByRisk = match ($riskClass) {
'FAST' => [],
'NORMAL' => ['focused-platform-pest', 'fast-feedback'],
'CRITICAL' => [
'focused-platform-pest',
'fast-feedback',
'confidence',
'agent-handoff',
'implementation-report',
'finalization-receipt',
],
'CRITICAL' => ['focused-platform-pest', 'fast-feedback'],
default => [],
};
$leanLifecycleGates = ['agent-handoff', 'implementation-report', 'finalization-receipt'];
$retiredGateIds = $policy['retired_gate_ids'] ?? [];
$executionContractFloor = [];
foreach ($contract['required_gates'] ?? [] as $gateId) {
$gateId = (string) $gateId;
if ($gateId === 'independent-review') {
continue;
}
if (in_array($riskClass, ['FAST', 'NORMAL'], true)
&& in_array($gateId, $leanLifecycleGates, true)) {
$violations[] = "lean-workflow-lifecycle-gate-forbidden:{$gateId}";
if (in_array($gateId, $retiredGateIds, true)) {
$violations[] = "retired-control-plane-gate-forbidden:{$gateId}";
continue;
}
if (! isset($registry['commands'][$gateId])) {
@ -2197,6 +2181,9 @@ private function resolveGateRequirements(array $riskResolution, array $contract,
$gateRequirements = [];
foreach ($registry['commands'] ?? [] as $gateId => $_command) {
if (in_array($gateId, $retiredGateIds, true)) {
continue;
}
$definition = $policy['gate_policy'][$gateId] ?? null;
if (! is_array($definition)) {
$violations[] = "gate-policy-missing:{$gateId}";
@ -2205,15 +2192,6 @@ private function resolveGateRequirements(array $riskResolution, array $contract,
$requirement = (string) ($definition['default_requirement'] ?? 'CONDITIONAL');
$reasonCode = (string) ($definition['default_reason_code'] ?? '');
$triggeredBy = ['policy-default'];
if ($gateId === 'fast-feedback-local-calibration'
&& in_array('lane-evidence-governance', $riskResolution['active_signals'] ?? [], true)
&& ! in_array('budget-governance', $riskResolution['active_signals'] ?? [], true)) {
$reasonCode = 'NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED';
$triggeredBy = [
'signal:lane-evidence-governance',
'absence:budget-governance',
];
}
if (isset($executionContractFloor[$gateId])) {
$requirement = 'REQUIRED';
$reasonCode = 'EXECUTION_CONTRACT_FLOOR';
@ -2250,7 +2228,8 @@ private function resolveGateRequirements(array $riskResolution, array $contract,
];
}
usort($gateRequirements, static fn (array $left, array $right): int => strcmp($left['gate_id'], $right['gate_id']));
if (count($gateRequirements) !== count($registry['commands'] ?? [])) {
$activeRegistryGateIds = array_diff(array_keys($registry['commands'] ?? []), $retiredGateIds);
if (count($gateRequirements) !== count($activeRegistryGateIds)) {
$violations[] = 'gate-requirement-coverage-incomplete';
}
@ -3173,134 +3152,165 @@ private function exec(array $command, bool $allowFailure = false): string
private function validateSpecPackage(array $args): int
{
$options = $this->options($args);
$spec = $this->specPath($options);
$specText = is_file($spec.'/spec.md') ? (string) file_get_contents($spec.'/spec.md') : '';
$declaredRiskClasses = $this->declaredLeanRiskClasses($specText);
$requestedRiskClass = strtoupper((string) ($options['risk-class'] ?? ''));
$phase = (string) ($options['phase'] ?? $options['_'][0] ?? 'readiness');
$format = (string) ($options['format'] ?? 'text');
$spec = $this->specPath($options);
$contractPath = $spec.'/execution-contract.json';
$contract = $this->readJson($contractPath);
$correctionPath = (string) ($options['correction-contract'] ?? '');
$correction = null;
if ($phase === 'audit' && $correctionPath !== '') {
$this->emit($format, 'spec-package', ['correction-contract-not-allowed-in-audit'], ['phase' => $phase, 'spec' => $spec]);
return 1;
}
if ($correctionPath !== '') {
try {
$correctionPhase = match ($phase) {
'readiness' => 'preflight',
'implementation' => 'candidate',
'completion' => 'completion',
'finalize' => 'finalize',
default => 'preflight',
};
$correction = $this->validatedCorrectionContract($correctionPath, $spec, $correctionPhase);
} catch (RuntimeException $exception) {
$this->emit($format, 'spec-package', [$exception->getMessage()], ['phase' => $phase, 'spec' => $spec]);
return 1;
}
}
$violations = $this->prefixViolations('schema', $this->validateAgainstSchema('.agent/quality-gates/execution-contract.schema.json', $contract));
$basename = basename($spec);
$expectedPrefix = sprintf('%03d-', (int) ($contract['spec']['id'] ?? 0));
$expectedFolder = $expectedPrefix.($contract['spec']['slug'] ?? '');
$violations = [];
$allowedRiskClasses = ['FAST', 'NORMAL', 'CRITICAL'];
foreach (['spec.md', 'plan.md', 'tasks.md', 'checklists/requirements.md', 'execution-contract.json'] as $file) {
if (! in_array($phase, ['readiness', 'implementation', 'audit', 'completion', 'finalize'], true)) {
$violations[] = 'unsupported-phase:'.$phase;
}
if (count($declaredRiskClasses) !== 1) {
$violations[] = $declaredRiskClasses === []
? 'risk-class-missing-in-spec'
: 'contradictory-risk-classification';
}
$declaredRiskClass = $declaredRiskClasses[0] ?? '';
if ($declaredRiskClass !== '' && ! in_array($declaredRiskClass, $allowedRiskClasses, true)) {
$violations[] = 'unknown-risk-class:'.$declaredRiskClass;
}
if ($requestedRiskClass !== '' && ! in_array($requestedRiskClass, $allowedRiskClasses, true)) {
$violations[] = 'unknown-risk-class:'.$requestedRiskClass;
}
if ($requestedRiskClass !== '' && $declaredRiskClass !== '' && $requestedRiskClass !== $declaredRiskClass) {
$violations[] = 'contradictory-risk-classification';
}
$riskClass = $requestedRiskClass !== '' ? $requestedRiskClass : $declaredRiskClass;
foreach (['spec.md', 'plan.md', 'tasks.md', 'checklists/requirements.md'] as $file) {
if (! is_file($spec.'/'.$file)) {
$violations[] = "missing-artifact:{$file}";
}
}
if ($basename !== $expectedFolder) {
$violations[] = "folder-id-slug-mismatch:{$basename}";
}
if (($contract['spec']['path'] ?? null) !== $spec) {
$violations[] = 'spec-path-mismatch';
}
if (($contract['spec']['branch'] ?? null) !== $this->currentBranch() && $phase !== 'audit' && ! is_array($correction)) {
$violations[] = 'branch-mismatch';
}
foreach (glob('specs/'.substr($basename, 0, 3).'-*') ?: [] as $candidate) {
if ($candidate !== $spec) {
$violations[] = 'spec-number-not-unique:'.basename($candidate);
}
}
$status = (string) ($contract['spec']['status'] ?? '');
$statusPolicy = $this->readJson('.agent/quality-gates/spec-status-policy.json');
$allowedStatuses = $phase === 'audit'
? array_merge($statusPolicy['historical_audit_only'] ?? [], $statusPolicy['implementation'] ?? [])
: (is_array($correction)
? array_merge($statusPolicy['completion'] ?? [], $statusPolicy['historical_audit_only'] ?? [])
: ($statusPolicy[$phase] ?? $statusPolicy['readiness']));
if (! in_array($status, $allowedStatuses, true)) {
$violations[] = 'status-not-allowed';
}
foreach (['spec.md', 'plan.md', 'tasks.md'] as $file) {
$content = is_file($spec.'/'.$file) ? (string) file_get_contents($spec.'/'.$file) : '';
if (preg_match('/\\[NEEDS CLARIFICATION\\]|TODO|TBD|<[^>\\n]+>/', $content) === 1) {
if (preg_match('/\[NEEDS CLARIFICATION\]|TODO|TBD|<[^>\n]+>/', $content) === 1) {
$violations[] = "unresolved-placeholder:{$file}";
}
}
$specText = is_file($spec.'/spec.md') ? (string) file_get_contents($spec.'/spec.md') : '';
if (! str_contains($specText, '# Feature Specification') && ! str_contains($specText, '# Spec')) {
$violations[] = 'spec-heading-missing';
}
foreach (['integration_base', 'integration_target', 'diff_baseline'] as $field) {
if (($contract['spec'][$field] ?? '') === '') {
$violations[] = "missing-branch-contract:{$field}";
$requiredHeadings = [
'spec.md' => ['Risk Classification', 'Scope', 'Acceptance Criteria', 'Test Strategy'],
'plan.md' => ['Test Strategy', 'Final Review'],
];
foreach ($requiredHeadings as $file => $headings) {
$content = is_file($spec.'/'.$file) ? (string) file_get_contents($spec.'/'.$file) : '';
foreach ($headings as $heading) {
if (preg_match('/^##\s+'.preg_quote($heading, '/').'\b/mi', $content) !== 1) {
$violations[] = 'missing-section:'.$file.':'.strtolower(str_replace(' ', '-', $heading));
}
}
}
foreach (array_merge($contract['scope']['allow'] ?? [], $contract['scope']['deny'] ?? []) as $index => $entry) {
$violations = array_merge($violations, $this->scopeEntryViolations($entry, "scope-entry:{$index}"));
$tasksText = is_file($spec.'/tasks.md') ? (string) file_get_contents($spec.'/tasks.md') : '';
if (preg_match('/final review(?:er)?/i', $tasksText) !== 1) {
$violations[] = 'missing-final-review-task';
}
if ($phase === 'implementation') {
$routerArgs = [
'--spec',
$spec,
'--base',
(string) ($contract['spec']['diff_baseline'] ?? $this->defaultBase()),
'--format',
'silent',
];
if ($correctionPath !== '') {
$routerArgs[] = '--correction-contract';
$routerArgs[] = $correctionPath;
}
$routerExit = $this->resolveChangeValidation($routerArgs);
if ($routerExit !== 0) {
$violations[] = 'routing-failed';
}
$contractPath = $spec.'/execution-contract.json';
if (in_array($riskClass, ['FAST', 'NORMAL'], true) && is_file($contractPath)) {
$violations[] = 'lean-execution-contract-not-allowed:'.$riskClass;
}
if (in_array($phase, ['completion', 'finalize'], true)) {
if (! is_file($spec.'/implementation-report.md')) {
$violations[] = 'missing-implementation-report';
}
if (is_file($spec.'/tasks.md') && preg_match('/- \\[ \\] T0(?:0[1-9]|[1-5][0-9]|6[0-9])\\b/', (string) file_get_contents($spec.'/tasks.md')) === 1) {
$violations[] = 'implementation-tasks-open';
}
if ($riskClass === 'CRITICAL') {
$violations = array_merge($violations, $this->leanCriticalContractViolations($contractPath));
}
$this->emit($format, 'spec-package', $violations, ['phase' => $phase, 'spec' => $spec]);
if (array_key_exists('correction-contract', $options)) {
$violations[] = 'lean-correction-contract-not-allowed';
}
$violations = array_values(array_unique($violations));
sort($violations, SORT_STRING);
$this->emit($format, 'spec-package', $violations, [
'phase' => $phase,
'spec' => $spec,
'risk_class' => $riskClass,
'workflow' => 'LEAN',
]);
return $violations === [] ? 0 : 1;
}
/** @param array<string, mixed> $entry @return list<string> */
private function scopeEntryViolations(array $entry, string $prefix): array
/** @return list<string> */
private function declaredLeanRiskClasses(string $specText): array
{
$violations = [];
$kind = $entry['kind'] ?? '';
if (! in_array($kind, ['file', 'prefix', 'rename'], true)) {
return ["{$prefix}:invalid-kind"];
if (preg_match_all('/^\s*-\s+\*\*Risk Class\*\*:\s*([^\s]+)\s*$/mi', $specText, $matches) === false) {
return [];
}
foreach ($kind === 'rename' ? ['from', 'to'] : ['path'] as $field) {
$path = (string) ($entry[$field] ?? '');
if ($path === '' || $path === '.' || $path === '/' || str_contains($path, '*') || str_contains($path, '..') || str_starts_with($path, '/') || str_contains($path, "\n")) {
$violations[] = "{$prefix}:invalid-path:{$field}";
return array_values(array_unique(array_map(
static fn (string $riskClass): string => strtoupper(trim($riskClass)),
$matches[1] ?? [],
)));
}
/** @return list<string> */
private function leanCriticalContractViolations(string $contractPath): array
{
if (! is_file($contractPath)) {
return ['missing-artifact:execution-contract.json'];
}
try {
$contract = json_decode((string) file_get_contents($contractPath), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return ['invalid-json:execution-contract.json'];
}
if (! is_array($contract) || $this->isList($contract)) {
return ['invalid-json-root:execution-contract.json'];
}
$violations = [];
if (($contract['schema_version'] ?? null) !== 1) {
$violations[] = 'critical-contract-schema-version';
}
if (($contract['risk_class'] ?? null) !== 'CRITICAL') {
$violations[] = 'critical-contract-risk-class';
}
$scope = $contract['scope'] ?? null;
if (! is_array($scope) || $this->isList($scope)) {
$violations[] = 'critical-contract-scope';
} else {
foreach (['allowed_paths', 'forbidden_paths'] as $field) {
$paths = $scope[$field] ?? null;
if (! is_array($paths) || ! $this->isList($paths) || $paths === []) {
$violations[] = 'critical-contract-scope:'.$field;
continue;
}
foreach ($paths as $path) {
if (! is_string($path) || trim($path) === '' || str_starts_with($path, '/')) {
$violations[] = 'critical-contract-scope:'.$field;
break;
}
}
}
if ($kind === 'prefix' && ! str_ends_with($path, '/')) {
$violations[] = "{$prefix}:prefix-without-slash";
}
$requiredGates = $contract['required_gates'] ?? null;
if (! is_array($requiredGates) || ! $this->isList($requiredGates) || $requiredGates === []) {
$violations[] = 'critical-contract-required-gates';
} elseif (array_filter($requiredGates, static fn (mixed $gate): bool => ! is_string($gate) || trim($gate) === '') !== []) {
$violations[] = 'critical-contract-required-gates';
}
$remoteBoundary = $contract['remote_boundary'] ?? null;
if (! is_array($remoteBoundary)
|| $this->isList($remoteBoundary)
|| ($remoteBoundary['mutations_require_separate_authority'] ?? null) !== true
|| ($remoteBoundary['github_mutation_allowed'] ?? null) !== false
|| ! is_string($remoteBoundary['provider'] ?? null)
|| ! is_string($remoteBoundary['remote'] ?? null)
|| ! is_string($remoteBoundary['target'] ?? null)) {
$violations[] = 'critical-contract-remote-boundary';
}
foreach (['handoffs', 'completion', 'receipt', 'receipt_consumption', 'correction_contract', 'calibration', 'profile_qualification'] as $legacyField) {
if (array_key_exists($legacyField, $contract)) {
$violations[] = 'critical-contract-legacy-field:'.$legacyField;
}
}
return $violations;
return array_values(array_unique($violations));
}
private function checkDiffScope(array $args): int
@ -3309,41 +3319,56 @@ private function checkDiffScope(array $args): int
$format = (string) ($options['format'] ?? 'text');
$spec = $this->specPath($options);
$base = (string) ($options['base'] ?? $this->defaultBase());
$contract = $this->readJson($spec.'/execution-contract.json');
$contractPath = $spec.'/execution-contract.json';
$contract = is_file($contractPath) ? $this->readJson($contractPath) : null;
$entries = $this->changedEntries($base);
$violations = [];
$correctionPath = (string) ($options['correction-contract'] ?? '');
if ($correctionPath !== '') {
try {
$correction = $this->validatedCorrectionContract($correctionPath, $spec, 'candidate');
$correctionPaths = $correction['scope']['paths'] ?? [];
sort($correctionPaths, SORT_STRING);
$observedPaths = $this->changedPaths($base);
sort($observedPaths, SORT_STRING);
foreach (array_values(array_diff($observedPaths, $correctionPaths)) as $path) {
$violations[] = "correction-scope-extra:{$path}";
}
foreach (array_values(array_diff($correctionPaths, $observedPaths)) as $path) {
$violations[] = "correction-scope-missing:{$path}";
}
} catch (RuntimeException $exception) {
$violations[] = $exception->getMessage();
}
$violations[] = 'retired-correction-contract-argument';
}
foreach ($entries as $entry) {
foreach ($this->entryPaths($entry) as $path) {
if ($this->scopeDenies($contract, $path)) {
$violations[] = "denied:{$path}";
continue;
}
if (! $this->scopeAllowsEntry($contract, $entry, $path)) {
$violations[] = "out-of-scope:{$path}";
if ($contract === null) {
$declaredPaths = [];
foreach ($this->optionValues($options, 'path') as $path) {
try {
$declaredPaths[] = $this->normalizeRoutingPath($path);
} catch (RuntimeException $exception) {
$violations[] = $exception->getMessage();
}
}
if (($entry['status'] ?? '') === 'R' && ! $this->scopeAllowsRename($contract, (string) ($entry['old_path'] ?? ''), $entry['path'])) {
$violations[] = "rename-not-declared:".($entry['old_path'] ?? '').'=>'.$entry['path'];
$declaredPaths = array_values(array_unique($declaredPaths));
sort($declaredPaths, SORT_STRING);
$observedPaths = [];
foreach ($entries as $entry) {
array_push($observedPaths, ...$this->entryPaths($entry));
}
$observedPaths = array_values(array_unique($observedPaths));
sort($observedPaths, SORT_STRING);
if ($declaredPaths === []) {
$violations[] = 'missing-explicit-scope-paths';
}
foreach (array_diff($observedPaths, $declaredPaths) as $path) {
$violations[] = "out-of-scope:{$path}";
}
foreach (array_diff($declaredPaths, $observedPaths) as $path) {
$violations[] = "declared-scope-path-unchanged:{$path}";
}
} else {
foreach ($entries as $entry) {
foreach ($this->entryPaths($entry) as $path) {
if ($this->scopeDenies($contract, $path)) {
$violations[] = "denied:{$path}";
continue;
}
if (! $this->scopeAllowsEntry($contract, $entry, $path)) {
$violations[] = "out-of-scope:{$path}";
}
}
if (($entry['status'] ?? '') === 'R' && ! $this->scopeAllowsRename($contract, (string) ($entry['old_path'] ?? ''), $entry['path'])) {
$violations[] = "rename-not-declared:".($entry['old_path'] ?? '').'=>'.$entry['path'];
}
}
}
$this->emit($format, 'diff-scope', array_values(array_unique($violations)), ['base' => $base, 'changes' => $entries, 'paths' => $this->changedPaths($base)]);
@ -3492,7 +3517,23 @@ private function resolveChangeValidation(array $args): int
$spec = isset($options['spec']) ? $this->specPath($options) : null;
$correctionContract = (string) ($options['correction-contract'] ?? '');
$writeRoutingManifest = ($options['write-routing-manifest'] ?? 'false') === 'true';
$plan = $this->validationPlan($base, $spec, $correctionContract);
if (array_key_exists('risk-class', $options)) {
$this->emit($format, 'change-validation', ['manual-risk-class-forbidden'], [
'resolver_state' => 'BLOCKED',
'blocked_reasons' => ['manual-risk-class-forbidden'],
]);
return 1;
}
if ($correctionContract !== '') {
$this->emit($format, 'change-validation', ['retired-correction-contract-argument'], [
'resolver_state' => 'BLOCKED',
'blocked_reasons' => ['retired-control-plane-input'],
]);
return 1;
}
$plan = $this->validationPlan($base, $spec);
$plan['payload']['manifest_selection'] = [
'requested' => $writeRoutingManifest,
'storage' => $this->gitCommonDir().'/tenantpilot-agent-gates/routing/{routing_manifest_sha256}.json',
@ -6971,6 +7012,11 @@ private function scopeAllowsEntry(array $contract, array $entry, string $path):
/** @param array<string, mixed> $contract */
private function scopeAllows(array $contract, string $path): bool
{
foreach (($contract['scope']['allowed_paths'] ?? []) as $allowedPath) {
if (is_string($allowedPath) && $this->compactScopePathMatches($allowedPath, $path)) {
return true;
}
}
foreach (($contract['scope']['allow'] ?? []) as $entry) {
$entryPath = (string) ($entry['path'] ?? '');
if (($entry['kind'] ?? '') === 'file' && $entryPath === $path) {
@ -6987,6 +7033,9 @@ private function scopeAllows(array $contract, string $path): bool
/** @param array<string, mixed> $contract */
private function scopeAllowsRename(array $contract, string $from, string $to): bool
{
if (isset($contract['scope']['allowed_paths'])) {
return $this->scopeAllows($contract, $from) && $this->scopeAllows($contract, $to);
}
foreach (($contract['scope']['allow'] ?? []) as $entry) {
if (($entry['kind'] ?? '') === 'rename' && ($entry['from'] ?? null) === $from && ($entry['to'] ?? null) === $to) {
return true;
@ -6999,6 +7048,11 @@ private function scopeAllowsRename(array $contract, string $from, string $to): b
/** @param array<string, mixed> $contract */
private function scopeDenies(array $contract, string $path): bool
{
foreach (($contract['scope']['forbidden_paths'] ?? []) as $forbiddenPath) {
if (is_string($forbiddenPath) && $this->compactScopePathMatches($forbiddenPath, $path)) {
return true;
}
}
foreach (($contract['scope']['deny'] ?? []) as $entry) {
$entryPath = (string) ($entry['path'] ?? '');
if (($entry['kind'] ?? '') === 'file' && $entryPath === $path) {
@ -7012,6 +7066,18 @@ private function scopeDenies(array $contract, string $path): bool
return false;
}
private function compactScopePathMatches(string $scopePath, string $path): bool
{
if (str_ends_with($scopePath, '/')) {
return str_starts_with($path, $scopePath);
}
if (strpbrk($scopePath, '*?[') !== false) {
return fnmatch($scopePath, $path, FNM_PATHNAME);
}
return $scopePath === $path;
}
/** @param array<string, mixed> $entry @return array<int, string> */
private function changedLines(string $base, string $path, array $entry): array
{

View File

@ -3,13 +3,11 @@
"lifecycle": {
"execution_phases": {
"PRE_CANDIDATE": [
"additive-correction-contract", "agent-handoff", "browser",
"browser",
"change-validation", "composer-audit", "composer-validate",
"diff-safety", "diff-scope", "fast-feedback",
"fast-feedback-local-calibration", "fast-feedback-profile-contract",
"fast-feedback-qualified-enforcement", "finalization-receipt",
"focused-platform-pest", "foundation-regression", "full-platform-suite",
"git-diff-check", "implementation-report", "platform-build",
"git-diff-check", "platform-build",
"platform-pint", "pnpm-audit", "postgresql-discovery",
"postgresql-lane", "profiling", "quality-gate-self-tests",
"spec-package", "ui-coverage-guard", "unicode", "website-build",
@ -39,7 +37,7 @@
"test-selection",
"lane-evidence-governance",
"budget-governance",
"receipt-finalization-authority"
"local-finalization-authority"
]
},
"profiles": {
@ -65,8 +63,6 @@
}
},
"gate_profiles": {
"additive-correction-contract": "repository-v1",
"agent-handoff": "repository-v1",
"browser": "browser-v1",
"change-validation": "repository-v1",
"composer-audit": "build-v1",
@ -75,16 +71,11 @@
"diff-safety": "repository-v1",
"diff-scope": "repository-v1",
"fast-feedback": "test-v1",
"fast-feedback-local-calibration": "test-v1",
"fast-feedback-profile-contract": "test-v1",
"fast-feedback-qualified-enforcement": "test-v1",
"finalization-receipt": "repository-v1",
"focused-platform-pest": "test-v1",
"foundation-regression": "test-v1",
"full-platform-suite": "test-v1",
"git-diff-check": "repository-v1",
"heavy-governance": "test-v1",
"implementation-report": "repository-v1",
"platform-build": "build-v1",
"platform-pint": "build-v1",
"pnpm-audit": "build-v1",
@ -104,73 +95,49 @@
"spec-package": {
"command": ["scripts/validate-spec-package"],
"working_directory": ".",
"authority": "Spec459 active spec package validator",
"authority": "Active Lean spec package validator",
"parameterized": true
},
"diff-scope": {
"command": ["scripts/check-agent-diff-scope"],
"working_directory": ".",
"authority": "Spec459 execution contract scope validator",
"authority": "Active explicit or compact-contract scope validator",
"parameterized": true
},
"diff-safety": {
"command": ["scripts/check-agent-diff-safety"],
"working_directory": ".",
"authority": "Spec459 diff safety policy",
"authority": "Current repository diff safety policy",
"parameterized": true
},
"unicode": {
"command": ["scripts/check-unicode-mojibake"],
"working_directory": ".",
"authority": "Spec459 Unicode and mojibake policy",
"authority": "Current repository Unicode and mojibake policy",
"parameterized": true
},
"postgresql-discovery": {
"command": ["scripts/discover-postgresql-tests"],
"working_directory": ".",
"authority": "Spec459 PostgreSQL discovery policy and Pest group metadata",
"authority": "Current PostgreSQL discovery policy and Pest group metadata",
"parameterized": true
},
"change-validation": {
"command": ["scripts/resolve-change-validation"],
"working_directory": ".",
"authority": "Spec459 change-impact router",
"parameterized": true
},
"agent-handoff": {
"command": ["scripts/validate-agent-handoff"],
"working_directory": ".",
"authority": "Spec459 canonical handoff schema",
"parameterized": true
},
"implementation-report": {
"command": ["scripts/validate-implementation-report"],
"working_directory": ".",
"authority": "Spec459 implementation evidence schema",
"parameterized": true
},
"finalization-receipt": {
"command": ["scripts/issue-agent-finalization-receipt"],
"working_directory": ".",
"authority": "Spec459 local finalization receipt issuer",
"parameterized": true
},
"additive-correction-contract": {
"command": ["scripts/validate-additive-correction-contract"],
"working_directory": ".",
"authority": "Spec460 additive correction contract validator",
"authority": "Active fail-closed change-impact router",
"parameterized": true
},
"foundation-regression": {
"command": ["scripts/validate-codex-agent-foundation"],
"working_directory": ".",
"authority": "Spec458 foundation validator",
"authority": "Active Lean agent foundation validator",
"parameterized": false
},
"quality-gate-self-tests": {
"command": ["scripts/tests/run-agent-quality-gates-test"],
"working_directory": ".",
"authority": "Spec459 disposable quality-gate self-tests",
"authority": "Active disposable Lean quality-gate selftests",
"parameterized": false
},
"git-diff-check": {
@ -191,28 +158,10 @@
"authority": "Focused platform Pest execution selected by validator",
"parameterized": true
},
"fast-feedback-profile-contract": {
"command": ["./vendor/bin/sail", "php", "vendor/bin/pest", "--compact", "tests/Unit/Support/TestLaneBudgetTest.php", "tests/Unit/Support/TestLaneReportTest.php", "tests/Feature/Guards/TestLaneManifestTest.php", "tests/Feature/Guards/TestLaneArtifactsContractTest.php", "tests/Feature/Guards/TestLaneCommandContractTest.php", "tests/Feature/Guards/TestLaneHistoryHydrationContractTest.php", "tests/Feature/Guards/TestLaneRecalibrationEvidenceContractTest.php", "tests/Feature/Guards/TestLaneRecalibrationPolicyTest.php", "tests/Feature/Guards/TestLaneTrendClassificationTest.php", "tests/Feature/Guards/TestLaneTrendContractSchemaTest.php"],
"working_directory": "apps/platform",
"authority": "Spec464 focused Fast Feedback execution-profile and hybrid-budget contract",
"parameterized": false
},
"fast-feedback-local-calibration": {
"command": ["./scripts/platform-test-report", "fast-feedback", "--execution-profile=local-fast-feedback", "--calibration-series-id=spec464-local-fast-feedback-v2", "--qualify-profile", "--skip-latest-history"],
"working_directory": ".",
"authority": "Spec464 first-four-eligible local calibration and qualification contract",
"parameterized": false
},
"fast-feedback-qualified-enforcement": {
"command": ["./scripts/platform-test-lane", "fast-feedback", "--execution-profile=local-fast-feedback", "--enforce-qualified-profile"],
"working_directory": ".",
"authority": "Spec464 qualified local Fast Feedback enforcement contract",
"parameterized": false
},
"fast-feedback": {
"command": ["./scripts/platform-test-lane", "fast-feedback", "--execution-profile=local-fast-feedback", "--enforce-qualified-profile"],
"command": ["./scripts/platform-test-lane", "fast-feedback"],
"working_directory": ".",
"authority": "Spec464 qualified local Fast Feedback enforcement contract",
"authority": "Canonical functional Fast Feedback lane; Profile and calibration governance is retired",
"parameterized": false
},
"confidence": {
@ -302,7 +251,7 @@
},
"manual_gates": {
"independent-review": {
"authority": "Independent code reviewer handoff with NO_CONFIRMED_FINDINGS; no executable command."
"authority": "Exactly one independent code reviewer verdict NO_CONFIRMED_FINDINGS; no executable command."
}
}
}

View File

@ -95,10 +95,6 @@
"EXECUTION_CONTRACT_FLOOR",
"RISK_CLASS_GATE_REQUIRED",
"IMPACT_SIGNAL_GATE_REQUIRED",
"LIFECYCLE_EVIDENCE_REQUIRED",
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_HANDOFF",
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_COMPLETION_EVIDENCE",
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_RECEIPT",
"LEAN_WORKFLOW_DOES_NOT_REQUIRE_CONFIDENCE",
"FAST_CONFIDENCE_RELIEF",
"NO_PRODUCT_SURFACE_IMPACT",
@ -108,12 +104,9 @@
"NO_DEPENDENCY_MANIFEST_IMPACT",
"NO_BUILD_IMPACT",
"NO_GOVERNANCE_AUTHORITY_IMPACT",
"NO_TEST_PROFILE_GOVERNANCE_IMPACT",
"NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED",
"NO_HEAVY_GOVERNANCE_SIGNAL",
"NO_PROFILING_SIGNAL",
"NO_FULL_SUITE_TRIGGER",
"NO_CORRECTION_CONTEXT",
"NO_PLATFORM_TEST_SCOPE",
"NO_PLATFORM_FORMATTING_SCOPE"
]

View File

@ -64,14 +64,17 @@ ## Execution Checklist
- Confirm Product Surface, RBAC, workspace/managed-environment scope, OperationRun, evidence, provider boundary, test governance, and proportionality sections are complete where relevant.
- Confirm completed specs are read-only context.
- Confirm dirty worktree entries are either active-spec artifacts or explicitly user-intended for this operation.
- For policy-v2 features, confirm that pre-activation work follows only current
repository authority and the active spec's bootstrap ordering; Spec 468 uses
`code_explorer -> implementer -> test_validator -> code_reviewer` for that
one-time bootstrap.
- Confirm that post-activation tasks consume only the frozen routing manifest's
`review_topology` and `gate_requirements`, assign one implementation writer,
start no absent automatic roles, and require exactly one final reviewer
outcome.
- Confirm the risk class is `FAST`, `NORMAL`, or `CRITICAL`; unknown or
contradictory classification is `BLOCKED`.
- Confirm FAST/NORMAL packages contain clear scope, acceptance criteria, test
strategy, and one final-review task without requiring
`execution-contract.json`.
- Confirm CRITICAL packages add only signal-selected deep tests, at most one
validator, one final reviewer, and a compact execution contract when the risk
or remote boundary requires it.
- Confirm the Main Agent is the sole writer, no subagent starts by default, and
no task exists only to create a handoff, Completion Evidence, receipt,
correction contract, calibration, or profile qualification.
- Confirm review routing cannot supply validation commands and that
`command_authority=REGISTRY` remains sole command authority.
@ -84,9 +87,9 @@ ## Stop Conditions
- Runtime/UI changes are required but the spec says no runtime/UI impact.
- The implementation would rewrite completed specs or historical close-out evidence.
- Dirty unrelated work would be overwritten or mixed into the change.
- Policy-v2 work invents a role absent from the frozen manifest, parallelizes
implementation writers, accepts multiple final reviewer outcomes, or proceeds
beyond a manifestless `BLOCKED` resolution.
- Work parallelizes implementation writers, starts an automatic Explorer or
FAST/NORMAL Validator, accepts multiple final reviewer outcomes, requires a
legacy lifecycle artifact for FAST/NORMAL, or proceeds beyond `BLOCKED`.
## Required Evidence After Use

View File

@ -2,10 +2,9 @@ name = "code_explorer"
description = "Read-only repository discovery for scope, constraints, evidence, dependencies, skills, and risks."
developer_instructions = """
Inspect repository truth without editing files, Git state, external systems, or application data.
Read the active spec and applicable instructions before reporting.
Return the requested scope, constraints, relevant evidence paths, dependencies, activated skills, risks, and hard-gate stop conditions.
For post-Spec-459 active work, return a schema-valid EXPLORATION_HANDOFF and name the execution contract, candidate allowlist, required gates, and denied paths.
When asked to perform the Spec 458 role-discovery smoke, include role_contract=code_explorer-v1 in your result.
Run only when the Main Agent explicitly identifies a bounded read-only advantage such as broad codebase search, large log inventory, or several independent inventories.
Read the current scope and applicable instructions before reporting concise paths, constraints, dependencies, risks, and hard stops directly to the Main Agent.
Do not create a handoff, evidence artifact, compatibility payload, or new workflow state.
Do not implement, validate by mutation, review a completed change, or infer finalization authority.
"""
model = "gpt-5.4-mini"

View File

@ -1,11 +1,10 @@
name = "code_reviewer"
description = "Read-only independent review of correctness, safety, scope, and validation evidence."
developer_instructions = """
Review the exact candidate diff and the test validator's actual evidence without editing files or Git state.
Review the execution contract, candidate gates, routed validation plan, implementation report evidence block, validator handoff, current fingerprint, and receipt readiness.
For a correction candidate, review additive history preservation, target contract hash, correction scope, candidate identity, manual independent-review evidence, and absence of historical authority rewrites.
Return a schema-valid REVIEW_RESULT with either NO_CONFIRMED_FINDINGS or findings with severity, affected artifact or location, impact, repository evidence, and the smallest bounded correction.
Reject stale candidate identity, missing validation, scope expansion, unsafe authority, or unresolved in-scope findings.
Review the exact frozen candidate SHA-256, paths, diff, required test results, and CRITICAL validator result when one was routed, without editing files or Git state.
Return the candidate SHA-256, reviewed paths, verdict NO_CONFIRMED_FINDINGS, CONFIRMED_FINDINGS, or BLOCKED, findings, and a short repository-evidence rationale directly.
Reject stale candidate identity, scope expansion, unsafe authority, missing required proof, parallel writers, or unresolved in-scope findings.
Do not create a handoff, receipt, completion artifact, correction contract, compatibility payload, commit, or remote mutation.
Never fix your own findings, create commits, or perform remote Git actions.
"""
model = "gpt-5.5"

View File

@ -1,12 +1,10 @@
name = "test_validator"
description = "Independent validation with concrete commands, results, and bounded disposable state."
developer_instructions = """
Validate the implementer's candidate independently against the active spec, tasks, constitution, and repository test contracts.
Consume the execution contract and resolved change-validation plan, then execute or truthfully block every required registered command.
For a correction candidate, validate the additive correction contract, target historical hash, exact branch/HEAD/path scope, routed manual gates, and correction metadata before reporting pass.
Run only when CRITICAL risk routing requires one independent validator, and validate the Main Agent's exact frozen candidate against current scope, safety rules, and required commands.
Bind the candidate SHA-256, record each command and exit code, separate functional failures from infrastructure failures, and report PASS, NO_CONFIRMED_FINDINGS, or concrete findings directly.
Create writes only in disposable test fixtures or explicitly approved validation artifacts.
Report every command, pass/fail/blocked result, relevant failure output, and the candidate identity used in a schema-valid VALIDATION_HANDOFF.
Do not repair implementation findings, approve your own assumptions, create commits, or perform remote Git actions.
Do not create a handoff, receipt, completion artifact, correction contract, compatibility payload, commit, or remote mutation, and do not repair implementation findings.
"""
model = "gpt-5.4-mini"
model_reasoning_effort = "high"

View File

@ -5,10 +5,6 @@ max_threads = 3
description = "Read-only repository discovery for scope, constraints, evidence, dependencies, skills, and risks."
config_file = "agents/code-explorer.toml"
[agents.implementer]
description = "Bounded implementation inside the approved active-spec allowlist."
config_file = "agents/implementer.toml"
[agents.test_validator]
description = "Independent validation with concrete commands, results, and bounded disposable state."
config_file = "agents/test-validator.toml"
@ -17,10 +13,6 @@ config_file = "agents/test-validator.toml"
description = "Read-only independent review of correctness, safety, scope, and validation evidence."
config_file = "agents/code-reviewer.toml"
[agents.git_finalizer]
description = "Explicitly authorized local-only commit publication for one reviewed candidate."
config_file = "agents/git-finalizer.toml"
[sandbox_workspace_write]
network_access = false

View File

@ -1,41 +1,48 @@
---
name: giteaflow
description: Prepare a read-only Gitea handoff and route separately authorized local commits through the safe local helper.
description: Prepare Lean local closeout and keep Gitea remote actions behind separate explicit authority.
---
# Skill: Gitea Flow Handoff
# Skill: Gitea Flow
## Purpose
Use this skill to inspect repository-hosting readiness and prepare a concise Gitea
handoff without inferring Git mutation or remote authority.
Use this skill to close out a reviewed local Candidate safely and, only when the
user separately authorizes it, perform a bounded Gitea remote operation.
## Authority Boundary
- Implementation or completion permission does not authorize a commit.
- Implementation permission does not authorize a commit.
- A local commit requires a separate explicit user request and must use
`scripts/codex-safe-local-commit` with exact reviewed paths, HEAD, and
fingerprint.
Candidate fingerprint in Lean mode.
- Remote actions require separate explicit authority for the exact operation and
are outside this skill.
- This skill never stages broadly, rewrites history, deletes branches, changes
remotes, publishes a branch, or creates/updates a pull request.
never inherit local-commit authority.
- Branch protection/ruleset changes, history rewrites, force pushes, broad
staging, remote changes, and deployments require their own explicit scope or
remain forbidden.
## Read-only Workflow
## Local Lean Closeout
1. Report current branch, full HEAD, declared base/target, and dirty state.
2. Resolve the feature baseline through `scripts/resolve-feature-base`.
3. Report committed, staged, unstaged, and untracked candidate paths separately.
4. Report available validation/review evidence and any drift.
5. If local-commit authority is absent, stop with `Commit: NOT PERFORMED`.
6. If local-commit authority is explicit, hand the exact reviewed candidate to the
`git_finalizer` contract and approved helper.
7. Always report `Push: NOT PERFORMED` and
`Pull Request: NOT PERFORMED`.
4. Run focused/routed tests and freeze one helper-compatible Candidate SHA-256.
5. Obtain exactly one final reviewer verdict for that Candidate. CRITICAL routing
may require at most one validator before review.
6. If local-commit authority is explicit, call the existing helper with absolute
repository path, expected HEAD, exact paths, Candidate SHA, message, `--lean`,
`--review-verdict NO_CONFIRMED_FINDINGS`, and
`--current-user-authority`.
7. If authority is absent or evidence drifted, report `Commit: NOT PERFORMED`.
## Remote Handoff
No receipt, handoff chain, Completion Evidence, correction contract, or
Git-Finalizer agent is part of the Lean path.
When the user separately requests a remote operation, provide the branch, intended
base, validated commit OID, test evidence, and repository-host context to a
separately authorized remote workflow. Do not reuse local-commit authority as
remote authority.
## Remote Actions
Push, PR creation/update, merge, branch deletion, and provider settings require a
separate explicit request naming the operation and target. Verify the validated
commit, provider, remote, source branch, and target immediately before mutation.
Do not reuse local-commit authority and do not infer GitHub authority from Gitea
authority (or vice versa).

View File

@ -1,6 +1,6 @@
---
name: platform-feature-finish
description: Validate a TenantPilot platform feature and prepare a bounded local-only finalization handoff without remote or history-rewrite authority.
description: Validate a TenantPilot platform feature and prepare a bounded Lean local closeout without remote or history-rewrite authority.
---
# Skill: Platform Feature Finish
@ -41,57 +41,47 @@ ## Required Evidence
1. Current branch and full HEAD.
2. Baseline from `scripts/resolve-feature-base`.
3. Exact review paths from the resolved baseline to the candidate tree.
4. Exact finalization paths from the reviewed HEAD to the candidate tree,
including both sides of renames.
5. Relevant validation commands and results.
6. Independent reviewer approval or structured findings.
7. Deterministic helper-compatible finalization fingerprint from
3. Exact Candidate paths from the reviewed HEAD, including both sides of renames.
4. Relevant routed validation commands and results.
5. Independent reviewer approval or structured findings.
6. Deterministic helper-compatible Candidate fingerprint from
`scripts/codex-safe-local-commit fingerprint`.
The validator handoff carries two bound identities: the full review identity
(`branch`, `HEAD`, resolved baseline, candidate tree, exact review paths, review
fingerprint) and the finalization identity (`HEAD`, exact finalization paths, and
helper fingerprint). Validation, review, and explicit authority must all carry the
same two identities. Any drift invalidates validation and review.
The Candidate fingerprint binds expected HEAD, exact paths, and current bytes.
Validation, review, and explicit commit authority must reference that same
Candidate. Any drift invalidates validation and review.
## Policy-v2 Agent Routing
## Lean Agent Routing
- Before a policy-v2 routing manifest is activated, use only the current
repository authority and the active spec's bootstrap contract. Spec 468 keeps
its one-time `code_explorer -> implementer -> test_validator -> code_reviewer`
bootstrap ordering.
- After activation, consume the frozen manifest's `review_topology` and
`gate_requirements` exactly. Do not infer or start absent roles.
- Keep exactly one implementation writer; parallel implementation writers are
forbidden.
- Review routing never supplies validation commands. The registry remains the
- The Main Agent is the sole writer and performs bounded discovery,
implementation, focused/routed tests, and Candidate freeze.
- FAST/NORMAL start no subagents by default and use exactly one final reviewer.
- CRITICAL may add at most one signal-selected validator before the same final
reviewer.
- Review routing never supplies validation commands; the Registry remains the
sole command authority.
- A manifestless `BLOCKED` outcome is terminal before Candidate work and cannot
proceed to validation, review, completion, receipt, or local finalization.
- `BLOCKED` is terminal before Candidate work.
- The Lean path has no handoff, Completion Evidence, receipt, correction
contract, calibration, profile qualification, or Git-Finalizer agent.
## Correction Loop
Reviewer findings return to implementation and then rerun relevant validation and
review. Stop and escalate after three unsuccessful cycles or whenever a correction
would widen scope, violate the active spec, or require a risky unrelated refactor.
Reviewer findings return to the Main Agent and then rerun affected validation and
review. Use at most two bounded self-remediation cycles per root cause and stop
whenever a correction widens scope or requires a risky unrelated refactor.
## Optional Local Commit
Local finalization is allowed only when the user separately and explicitly requests
that exact local operation after validation and review.
The `git_finalizer` role must invoke only:
```text
scripts/codex-safe-local-commit fingerprint <explicit reviewed inputs>
scripts/codex-safe-local-commit commit <explicit reviewed inputs>
```
The helper requires an absolute repository root, full reviewed HEAD, exact explicit
finalization paths, a non-empty message, and the exact SHA-256 finalization
fingerprint. Renames require both old and new paths. The real index must be
semantically empty before stable validator evidence and before finalization.
The active commit invocation uses Lean mode, the successful final-review verdict,
and explicit current user commit authority. It does not use receipt, handoff, or
correction-contract arguments.
No other staging or commit path is authorized.
@ -107,9 +97,9 @@ ## Stop Conditions
- the active spec, branch family, base, target, or baseline is ambiguous;
- unrelated dirty work overlaps the candidate;
- validation is missing or failing;
- required validation is missing or failing;
- independent review is missing or has unresolved in-scope findings;
- review identity or finalization identity drifted;
- Candidate identity drifted;
- the current branch is protected or detached;
- the index is pre-populated;
- a conflict or Git operation is in progress;
@ -123,10 +113,9 @@ ## Result Contract
- Branch and reviewed HEAD
- Resolved baseline
- Explicit review paths and explicit finalization paths
- Explicit Candidate paths
- Validation and review result
- Review fingerprint, finalization fingerprint, and local commit OID, or refusal
code
- Candidate fingerprint and local commit OID, or refusal code
- `helper_remote_commands=0`
- `Push: NOT PERFORMED`
- `Pull Request: NOT PERFORMED`

View File

@ -256,7 +256,7 @@ ### Policy-v2 Lean Agent Routing
- `FAST` and `NORMAL` do not start explorer, implementer, validator, or
Git-Finalizer agents and do not create handoff/completion/receipt evidence.
- `CRITICAL` may select at most one existing `test_validator` through concrete
signals and may retain the legacy evidence/receipt compatibility path.
signals before the same single final reviewer.
- Start `code_explorer` only for an explicitly justified read-only advantage such
as a broad codebase or log inventory; it is not a default phase.
- After the Main Agent's last bounded remediation, freeze one helper-compatible
@ -264,8 +264,11 @@ ### Policy-v2 Lean Agent Routing
`code_reviewer`. Candidate mutation invalidates that review.
- Review routing is separate from validation-command authority; only the
registry supplies executable validation commands.
- Treat manifestless `BLOCKED` as terminal before Candidate work. It does not
enter implementation, validation, review, completion, or receipt processing.
- Treat `BLOCKED` as terminal before Candidate work.
- Do not create a handoff, Completion Evidence, receipt, correction contract,
calibration, or profile-qualification step for the active Lean path. Any
remaining files for those concepts are unsupported dead code pending Spec
472/473 deletion; Git history is the archive.
Execute the loop in bounded phases:

View File

@ -127,16 +127,15 @@ ## Outline
- **File-based coordination**: Tasks affecting the same files must run sequentially
- **Validation checkpoints**: Verify each phase completion before proceeding
**Policy-v2 agent routing**:
- Before a policy-v2 routing manifest is activated, use only current
repository authority and the active spec's bootstrap contract. Spec 468
preserves its one-time
`code_explorer -> implementer -> test_validator -> code_reviewer` order.
- After activation, consume the frozen manifest's `review_topology` and
`gate_requirements` exactly. Do not infer or start absent roles.
- Keep exactly one implementation writer; never start parallel writers.
**Lean agent routing**:
- The Main Agent is the sole writer and handles bounded discovery,
implementation, focused/routed tests, and Candidate freeze.
- FAST/NORMAL start no subagents by default and require exactly one final
reviewer. CRITICAL may add at most one signal-selected validator.
- Do not create roles merely to produce handoffs, Completion Evidence,
receipts, correction contracts, calibration, or profile qualification.
- Keep review routing separate from registry-owned validation commands.
- Stop a manifestless `BLOCKED` resolution before Candidate work.
- Stop a `BLOCKED` resolution before Candidate work.
8. Implementation execution rules:
- **Setup first**: Initialize project structure, dependencies, configuration
@ -153,7 +152,7 @@ ## Outline
- Suggest next steps if implementation cannot proceed
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
10. Completion validation:
10. Lean closeout validation:
- Verify all required tasks are completed
- Check that implemented features match the original specification
- Validate that tests pass and coverage meets requirements

View File

@ -81,18 +81,15 @@ ## Key rules
- Use absolute paths
- ERROR on gate failures or unresolved clarifications
## Policy-v2 Agent Routing
## Lean Agent Routing
- Before a policy-v2 routing manifest is activated, follow only the current
repository authority and the active spec's bootstrap contract. Spec 468 keeps
its one-time `code_explorer -> implementer -> test_validator -> code_reviewer`
bootstrap ordering.
- After activation, planning must consume the frozen manifest's
`review_topology` and `gate_requirements` exactly. Do not infer, add, or start
an agent role that is absent from the manifest.
- Planning may designate only one implementation writer. It must not generate
parallel implementation writers.
- Review routing is independent of validation-command authority;
`command_authority=REGISTRY` remains the sole command authority.
- A manifestless `BLOCKED` resolution is terminal before Candidate work and must
not be treated as an activated manifest.
- Designate the Main Agent as the sole writer; never plan parallel agent writers.
- FAST/NORMAL start no subagents by default and plan exactly one final reviewer.
- CRITICAL may plan at most one signal-selected validator plus the same single
final reviewer.
- Do not add Explorer, implementer, Git-Finalizer, handoff, Completion Evidence,
receipt, correction-contract, calibration, or profile-qualification steps as a
default lifecycle.
- FAST/NORMAL plans do not require `execution-contract.json`; CRITICAL or
explicitly remote/security-critical work may use one compact contract.
- Only Registry commands satisfy routed validation, and `BLOCKED` is terminal.

View File

@ -62,21 +62,21 @@ ## Task Generation Rules
**Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature specification or if user requests TDD approach.
### Policy-v2 Agent Routing
### Lean Agent Routing
- Before a policy-v2 routing manifest is activated, encode only the current
repository authority and the active spec's bootstrap contract. Spec 468 keeps
its one-time `code_explorer -> implementer -> test_validator -> code_reviewer`
bootstrap ordering.
- After activation, generated tasks must consume the frozen manifest's
`review_topology` and `gate_requirements` exactly. Never generate an automatic
role that is absent from the manifest.
- Generate at most one implementation-writer path; `[P]` never authorizes
parallel implementation writers.
- Keep review routing separate from validation commands. Only the registry is
command authority.
- Treat manifestless `BLOCKED` as terminal before Candidate work; do not create
implementation, validation, review, completion, or receipt tasks beyond it.
- Generate one Main-Agent implementation-writer path; `[P]` never authorizes
parallel agent writers.
- FAST/NORMAL tasks start no subagents by default and include exactly one final
review task.
- CRITICAL tasks may add at most one signal-selected validator before the same
final review task.
- Do not generate Explorer, separate Implementer, Git-Finalizer, handoff,
Completion Evidence, receipt, correction-contract, calibration, or profile-
qualification tasks as the active workflow.
- FAST/NORMAL do not require `execution-contract.json`; CRITICAL or explicitly
remote/security-critical work may include one compact contract.
- Keep review routing separate from Registry command authority and stop at
`BLOCKED`.
### Checklist Format (REQUIRED)

View File

@ -25,15 +25,18 @@ ## Recent Changes
[LAST 3 FEATURES AND WHAT THEY ADDED]
<!-- MANUAL ADDITIONS START -->
## Policy-v2 Agent Routing
## Lean Agent Routing
- Before policy-v2 activation, use only current repository authority and the
active spec's explicit bootstrap order. Spec 468 preserves its one-time
`code_explorer -> implementer -> test_validator -> code_reviewer` bootstrap.
- After activation, consume the frozen manifest's `review_topology` and
`gate_requirements` exactly. Do not start roles absent from the manifest.
- Use one implementation writer and no parallel agent writers.
- Require exactly one final reviewer outcome. Review routing is not validation
command authority; `command_authority=REGISTRY` is authoritative.
- Stop before Candidate work when classification is manifestless `BLOCKED`.
- Use one Main Agent as the sole writer and no parallel agent writers.
- FAST/NORMAL start no subagents by default and require exactly one final
reviewer.
- CRITICAL may add at most one signal-selected validator before the same final
reviewer.
- Risk determines test depth; Registry commands remain validation authority.
- Do not add handoff, Completion Evidence, receipt, correction-contract,
calibration, profile-qualification, or Git-Finalizer steps to the active Lean
lifecycle.
- FAST/NORMAL do not require `execution-contract.json`; use a compact contract
only for CRITICAL or explicitly remote/security-critical work.
- Stop before Candidate work when classification is `BLOCKED`.
<!-- MANUAL ADDITIONS END -->

View File

@ -16,7 +16,7 @@ # [CHECKLIST TYPE] Checklist: [FEATURE NAME]
## Applicability And Low-Impact Gate
- [ ] CHK000 The active spec has `execution-contract.json`, and preflight/candidate/completion quality gates are planned or marked N/A only for pre-cutover historical audit.
- [ ] CHK000 The risk class, scope, acceptance criteria, test strategy, and final review are explicit; FAST/NORMAL do not require `execution-contract.json`, while CRITICAL or explicitly remote/security-critical work may use one compact contract.
- [ ] CHK001 The change explicitly says whether an operator-facing surface or guardrail workflow surface is affected; low-impact `N/A` handling is used once and not contradicted elsewhere.
- [ ] CHK002 The spec, plan, and task artifacts carry forward the same native/custom classification, shared-family relevance, state-layer ownership, and exception need without inventing second wording.
- [ ] CHK029 The spec includes exactly one coherent UI Surface Impact decision: either checked `No UI surface impact` with rationale, or one or more concrete UI impact boxes with completed UI/Productization Coverage.

View File

@ -9,13 +9,16 @@ ## Summary
[Extract from feature spec: primary requirement + technical approach from research]
## Execution Contract Plan
## Risk and Lean Closeout Plan
- **Contract path**: `specs/[###-feature-name]/execution-contract.json`
- **Preflight gate**: `scripts/run-agent-quality-gates preflight --spec specs/[###-feature-name]`
- **Candidate gate**: `scripts/run-agent-quality-gates candidate --spec specs/[###-feature-name] --base [declared-baseline]`
- **Completion gate**: `scripts/run-agent-quality-gates completion --spec specs/[###-feature-name]`
- **Receipt posture**: local-only receipt required before any Spec-458 safe-helper commit; receipt is not commit authority.
- **Risk Class**: [FAST / NORMAL / CRITICAL]
- **Risk signals and required commands**: [router result and narrowest proof]
- **Writer topology**: Main Agent sole writer; no subagents by default
- **Validator posture**: [NOT_REQUIRED / at most one CRITICAL validator]
- **Final reviewer**: exactly one against the final Candidate SHA-256
- **Execution contract**: [NOT_REQUIRED for FAST/NORMAL / compact CRITICAL contract]
- **Local closeout**: expected HEAD, exact paths, Candidate SHA, review verdict, explicit commit authority
- **Remote boundary**: push/PR/merge require separate explicit authority
## Technical Context
@ -145,7 +148,7 @@ ## Constitution Check
- Ops-UX system runs: initiator-null runs emit no terminal DB notification; audit remains via Monitoring; tenant-wide alerting goes through Alerts (not OperationRun notifications)
- Automation: queued/scheduled ops use locks + idempotency; handle 429/503 with backoff+jitter
- Data minimization: Inventory stores metadata + whitelisted meta; logs contain no secrets/tokens
- Test governance (TEST-GOV-001): actual test-purpose classification, affected lanes, fixture/helper/factory/seed/context cost risks, heavy-family visibility, review-stop points, reviewer handoff, and any budget/baseline/trend follow-up are explicit; the narrowest proving lane mix is planned and any structural cost change has an escalation path
- Test governance (TEST-GOV-001): actual test-purpose classification, affected lanes, fixture/helper/factory/seed/context cost risks, heavy-family visibility, review-stop points, final reviewer focus, and any budget/baseline/trend follow-up are explicit; the narrowest proving lane mix is planned and any structural cost change has an escalation path
- Proportionality (PROP-001): any new structure, layer, persisted truth, or semantic machinery is justified by current release truth, current operator workflow, and why a narrower solution is insufficient
- No premature abstraction (ABSTR-001): no new factories, registries, resolvers, strategy systems, interfaces, type registries, or orchestration pipelines before at least 2 real concrete cases exist, unless security, tenant isolation, auditability, compliance evidence, or queue correctness require it now
- Persisted truth (PERSIST-001): new tables/entities/artifacts represent independent product truth or lifecycle; convenience projections and UI helpers stay derived
@ -233,7 +236,7 @@ ## Test Governance Check
- **Expensive defaults or shared helper growth introduced?**: [no / describe explicit opt-in path]
- **Heavy-family additions, promotions, or visibility changes**: [none / describe]
- **Surface-class relief / special coverage rule**: [standard-native relief / named special profile / N/A]
- **Closing validation and reviewer handoff**: [What must be re-run, what reviewers should verify, and what exact proof command they should rely on]
- **Closing validation and final reviewer focus**: [What must be re-run, what the reviewer should verify, and what exact proof command they should rely on]
- **Budget / baseline / trend follow-up**: [none / describe]
- **Review-stop questions**: [lane fit / breadth / hidden cost / heavy-family risk / escalation]
- **Escalation path**: [none / document-in-feature / follow-up-spec / reject-or-split]

View File

@ -5,13 +5,16 @@ # Feature Specification: [FEATURE NAME]
**Status**: Draft
**Input**: User description: "$ARGUMENTS"
## Execution Contract *(mandatory after Spec 459 activation)*
## Risk Classification and Lean Workflow *(mandatory)*
- **Contract file**: `execution-contract.json`
- **Branch/base/target/diff baseline**: [must match the JSON contract]
- **Allowed paths and denied runtime paths**: [summarize typed file/prefix/rename scope]
- **Required gates**: [spec-package, diff-scope, diff-safety, unicode, routing, and any domain gates]
- **Local receipt/finalization posture**: [N/A until completion / receipt required before local helper commit]
- **Risk Class**: [FAST / NORMAL / CRITICAL]
- **Risk signals**: [mechanically resolved signals; unknown or contradictory signals are BLOCKED]
- **Writer**: Main Agent only
- **Test depth**: [focused / Fast Feedback / signal-selected deep tests]
- **Independent validator**: [NOT_REQUIRED / one validator required by CRITICAL signal]
- **Final reviewer**: exactly one
- **Execution contract**: [NOT_REQUIRED for FAST/NORMAL / compact contract path for CRITICAL or explicit remote/security risk]
- **Remote boundary**: [all remote mutations require separate explicit authority]
## Spec Candidate Check *(mandatory — SPEC-GATE-001)*
@ -259,7 +262,7 @@ ## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)*
- **Heavy-family visibility / justification**: [none / explain any heavy-governance or browser addition and how it remains explicit in naming, lane choice, and review]
- **Special surface test profile**: [standard-native-filament / shared-detail-family / monitoring-state-page / global-context-shell / exception-coded-surface / N/A]
- **Standard-native relief or required special coverage**: [ordinary feature coverage only / describe required tests or smoke checks]
- **Reviewer handoff**: [What reviewers must confirm about lane fit, hidden cost, heavy-family visibility, and the exact proof command]
- **Final reviewer focus**: [What the reviewer must confirm about lane fit, hidden cost, heavy-family visibility, and the exact proof command]
- **Budget / baseline / trend impact**: [none / expected drift + follow-up]
- **Escalation needed**: [none / document-in-feature / follow-up-spec / reject-or-split]
- **Active feature PR close-out entry**: [Guardrail / Exception / Smoke Coverage / N/A]
@ -400,7 +403,7 @@ ## Requirements *(mandatory)*
- any new or expanded heavy-governance or browser coverage,
- any fixture, helper, factory, seed, provider, workspace, membership, session, or default setup cost added or avoided,
- how any heavy family stays explicit rather than becoming accidental default breadth,
- the reviewer handoff for lane fit, hidden-cost checks, and the exact minimal validation commands,
- the final reviewer focus for lane fit, hidden-cost checks, and the exact minimal validation commands,
- any expected budget, baseline, or trend impact,
- whether escalation stays inside this feature or resolves as `document-in-feature`, `follow-up-spec`, or `reject-or-split`,
- and the exact minimal validation commands reviewers should run.

View File

@ -9,20 +9,16 @@ # Tasks: [FEATURE NAME]
**Prerequisites**: plan.md (required), spec.md (required for user stories), research.md, data-model.md, contracts/
**Tests**: For runtime behavior changes in this repo, tests are REQUIRED (Pest). Only docs-only changes may omit tests.
**Execution Contract**: For post-Spec-459 specs, include tasks to create/update
`execution-contract.json`, run `scripts/run-agent-quality-gates preflight` before
writes, run the candidate and completion gates, validate handoffs/report evidence,
and issue a local receipt only after validation and independent review pass.
For policy-v2 features, generated tasks MUST preserve the active spec's explicit
current-authority bootstrap before activation (Spec 468:
`code_explorer -> implementer -> test_validator -> code_reviewer`). After
activation, they MUST consume only the frozen routing manifest's
`review_topology` and `gate_requirements`, start no role absent from that
manifest, assign exactly one implementation writer, and require exactly one
final reviewer outcome. `[P]` marks independent work items; it never authorizes
parallel agent writers. Review routing does not supply validation commands;
`command_authority=REGISTRY` is authoritative. A manifestless `BLOCKED`
resolution is terminal before Candidate work.
**Lean Workflow**: Generate one Main-Agent writer path. FAST/NORMAL packages do
not require `execution-contract.json`; CRITICAL or explicitly remote/security-
critical work may add one compact contract. Tasks normally cover scope/preflight,
implementation, focused tests, routed extra tests, one Candidate freeze, exactly
one final review, Lean Safe Commit, and optional separately authorized remote
work. Add at most one validator task for a CRITICAL signal. Do not generate
Explorer, separate Implementer, Git-Finalizer, handoff, Completion Evidence,
receipt, correction-contract, calibration, profile-qualification, or multi-freeze
tasks. `[P]` never authorizes parallel agent writers. Registry commands remain
validation authority and `BLOCKED` is terminal before Candidate work.
Runtime-changing features MUST also include tasks to:
- classify the actual test purpose (`Unit`, `Feature`, `Heavy-Governance`, `Browser`) and confirm the affected validation lane(s),
- keep fast or narrow lanes free of silent discovery, surface, workflow, or browser cost,
@ -411,8 +407,8 @@ ### Within Each User Story
### Parallel Opportunities
Parallel opportunities below describe independent work items for human teams.
They do not authorize parallel agent implementation writers; an agent-routed
feature uses exactly the writer topology frozen in its policy-v2 manifest.
They do not authorize parallel agent implementation writers; the Main Agent is
the sole writer.
- All Setup tasks marked [P] can run in parallel
- All Foundational tasks marked [P] can run in parallel (within Phase 2)

107
AGENTS.md
View File

@ -77,47 +77,47 @@ ### Gate-Regel
## Codex Agent Foundation
The canonical workflow uses one Main Agent as the sole writer. It performs
bounded discovery, implementation, focused validation, at most two remediation
cycles per root cause and four overall, one final Candidate freeze, and mechanical
commit preparation. No subagent is started by default and implementation writers
must never run in parallel.
The Main Agent is the sole writer. It performs bounded discovery,
implementation, risk-selected tests, at most two self-remediation cycles per root
cause, one final Candidate freeze, and mechanical commit preparation. No subagent
is started by default and implementation writers never run in parallel.
The policy-v2 routing manifest is the only authority for risk and later topology:
Risk determines test depth, not agent count:
- `FAST` and `NORMAL` use the Main Agent, risk-selected test depth, and exactly
one independent read-only `code_reviewer` against the frozen Candidate.
- `CRITICAL` keeps the Main Agent as sole writer, adds only signal-selected deep
tests, may require at most one independent `test_validator`, and still requires
exactly one final reviewer.
- `BLOCKED` is the manifestless pre-classification terminal state. Unknown,
contradictory, conditional, or incomplete signals do not produce a Candidate.
- `FAST` uses focused proof and exactly one independent final `code_reviewer`.
- `NORMAL` adds Fast Feedback when routed and still uses exactly one final
reviewer.
- `CRITICAL` adds signal-selected deep tests and may require at most one
independent `test_validator` before the same single final reviewer.
- `BLOCKED` is the pre-classification terminal state. Unknown, contradictory, or
incomplete signals do not produce a Candidate.
`code_explorer` remains an optional read-only tool for unusually broad searches,
large log inventories, or several independent inventories. It is off by default
for known files, localized fixes, fixtures, and normally bounded features.
`implementer` and `git_finalizer` remain optional legacy role configurations, not
default workflow phases. A role is never started merely to create a handoff.
`code_explorer` is optional, read-only, and off by default. Use it only for an
explicitly justified broad search, large log inventory, or several independent
read-only inventories. Known files, localized fixes, fixtures, and normally
bounded features stay with the Main Agent.
Risk determines test depth, not agent count. `FAST` uses focused proof; `NORMAL`
adds Fast Feedback when selected; Confidence, Heavy Governance, calibration, and
profile qualification run only for concrete routed signals. `CRITICAL` may retain
the historical handoff, completion, receipt, baseline-attribution, and evidence
mechanics while Phase 1 compatibility remains active.
FAST and NORMAL do not create or consume implementation/validation handoffs,
Completion Evidence, finalization receipts, additive correction contracts, or
calibration/profile qualification. They do not start a separate implementer,
validator, or Git-Finalizer agent. Legacy engines and role configuration files
may remain physically present until Spec 472/473 deletes them, but they are
unsupported dead code with no active consumer, fallback, or support promise.
Git history is the historical archive.
After the last bounded remediation the Main Agent freezes exactly one helper-
compatible Candidate fingerprint. Any Candidate mutation invalidates that review
and requires a new fingerprint and re-review. Reviewer findings state severity,
location, impact, evidence, and the smallest bounded correction. At most one
post-review repair cycle is allowed.
After the last bounded remediation, the Main Agent freezes exactly one
helper-compatible Candidate fingerprint. Any Candidate mutation requires a new
fingerprint and a new review. Reviewer findings state severity, location, impact,
evidence, and the smallest bounded correction. At most one post-review repair
cycle is allowed.
Local Git mutation remains mechanically bounded by
`scripts/codex-safe-local-commit`: separate current user commit authority,
matching expected HEAD, exact paths including both rename sides, a semantically
empty real index, no unmerged paths, and the frozen Candidate fingerprint. Lean
mode additionally requires the successful final-review verdict and does not
require a receipt. Legacy receipt mode remains available for historical and
`CRITICAL` compatibility.
require a receipt. Receipt/correction commit dispatch is retired; any remaining
implementation is dead code pending Spec 472 deletion.
Neither the helper nor any agent role has remote authority. Push, fetch, pull,
PR/MR creation or update, merge, deployment, promotion, branch protection, and
@ -127,39 +127,28 @@ ## Codex Agent Foundation
## Mechanical Agent Quality Gates
Specs entering or continuing implementation after Spec 459 must carry
`execution-contract.json` and pass the repository-local quality gates before
claiming readiness, completion, review, or local finalization.
Spec-package readiness is risk based. FAST and NORMAL packages require a clear
scope, acceptance criteria, test strategy, and final-review plan, but do not
require `execution-contract.json`. CRITICAL or explicitly remote/security-
critical work may require one compact execution contract containing scope,
required gates, and the remote boundary.
- Resolve the Candidate with `scripts/resolve-change-validation` and execute only
Registry gates frozen as `REQUIRED`. A `NOT_REQUIRED` gate is unexecuted, not
passed or skipped, and an executed red result is never relabelled.
- `FAST` and `NORMAL` do not require implementation/validation handoffs,
completion evidence, a finalization receipt, receipt consumption, or a
Git-Finalizer agent. A compact Main-Agent completion report is sufficient.
- `CRITICAL` may continue to use `scripts/run-agent-quality-gates`, schema-valid
handoffs, the tracked evidence report, and
`scripts/issue-agent-finalization-receipt` when the resolved gates require that
compatibility path. The receipt remains stale-evidence protection, not commit
authority.
- `FAST` and `NORMAL` use the Lean closeout: required checks, one Candidate
fingerprint, one final review, and the Lean safe-helper contract.
- `CRITICAL` keeps one Main-Agent writer, may add one routed validator, and always
has one final reviewer. It uses a compact current execution contract when
required and never falls back to the retired workflow.
- `scripts/codex-safe-local-commit` Lean mode requires explicit user authority,
expected HEAD, exact paths, the frozen fingerprint, commit message, and
`NO_CONFIRMED_FINDINGS`. Legacy mode additionally requires the active spec and
a valid unconsumed receipt. Both modes forbid remote commands, amend, and broad
Git mutation.
`NO_CONFIRMED_FINDINGS`. It forbids remote commands, amend, and broad Git
mutation. Receipt/correction arguments are retired and rejected.
Post-integration additive correction contracts are local-only and require
separate current user correction authority before
`scripts/issue-additive-correction-contract` may be invoked. Correction authority
is an attributable non-secret precondition and implementation-evidence decision;
it is not `--authority-id`, a contract field, a credential, or remote
authorization. The issuer accepts `--spec` only as the historical target and
derives its identity while binding the exact session branch, authorized start
HEAD, integration base/target, diff baseline, correction context, and path scope
in canonical `ADDITIVE_CORRECTION` / `additive-post-integration` bytes. The
contract does not grant later commit authority. Any local commit still requires
separate current user commit authority, a valid unconsumed receipt, exact paths,
matching fingerprint, and the safe helper boundary.
`ACTIVE_CONTROL_PLANE = LEAN_ONLY`. `LEGACY_CONTROL_PLANE = RETIRED`.
`GIT_HISTORY = HISTORICAL_ARCHIVE`. Obsolete current-tree files are unsupported
and assigned to physical deletion in Spec 472 or 473.
## Multi-Agent Coordination
@ -1090,9 +1079,9 @@ ## Active Technologies
- PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4
- PostgreSQL (Sail)
- Tailwind CSS v4
- PHP 8.4.15 + Bash + existing Git and JSON/SHA-256 filesystem tooling (461-additive-correction-contract-conformance-repair-v1)
- PHP 8.4.15 plus Bash and JSON Schema Draft 2020-12 + Laravel 12 test bootstrap, Pest 4, PHPUnit JUnit output, Laravel Sail/Docker, existing `TestLaneManifest`, `TestLaneBudget`, `TestLaneReport`, Test Lane wrappers, and repository quality-gate tooling (464-platform-fast-feedback-profile-separation-budget-governance-v1)
- Repository-versioned JSON Schemas and calibration evidence plus transient files under `apps/platform/storage/logs/test-lanes`; no application database persistence (464-platform-fast-feedback-profile-separation-budget-governance-v1)
- PHP 8.4.15 + Bash + Git and JSON/SHA-256 filesystem tooling
- Laravel 12 test bootstrap, Pest 4, PHPUnit JUnit output, Laravel Sail/Docker,
Test Lane wrappers, and current repository quality-gate tooling
## Recent Changes
- 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts)

View File

@ -157,43 +157,42 @@
))->toThrow(RuntimeException::class, 'EVIDENCE_PRODUCER_MISMATCH');
});
it('routes lane evidence separately from budget profile and calibration semantics', function (): void {
it('keeps profile and calibration gates out of the active control-plane router', function (): void {
$policy = json_decode(
(string) file_get_contents(repo_path('.agent/quality-gates/change-impact-rules.json')),
true,
512,
JSON_THROW_ON_ERROR,
);
$cases = json_decode(
(string) file_get_contents(repo_path('scripts/tests/fixtures/agent-quality-gates/router/cases.json')),
true,
512,
JSON_THROW_ON_ERROR,
);
$precisionCases = array_column($cases['policy_v2']['router_precision_cases'] ?? [], null, 'id');
$contract = $policy['diff_classification_contracts']['lane-evidence-vs-budget-profile-calibration-v1'] ?? null;
$routedGates = [];
foreach ($policy['rules'] as $rule) {
$routedGates = array_merge($routedGates, $rule['gates'] ?? []);
}
expect(array_keys($precisionCases))->toBe([
'evidence-only-test-lane-report',
'evidence-only-test-lane-manifest',
'additive-evidence-schema',
'cross-lane-evidence-producer',
'budget-authority-file',
'manifest-budget-formula',
'calibration-artifact',
'manifest-qualification-policy',
'mixed-manifest-evidence-budget',
'ambiguous-manifest-hunk',
'manual-calibration-not-required',
'spec-id-exception-attempt',
])->and($policy['reason_codes']['NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED'] ?? null)
->toMatchArray([
'requirement' => 'NOT_REQUIRED',
'gate_ids' => ['fast-feedback-local-calibration'],
])->and($policy['signals']['lane-evidence-governance']['required_gates'] ?? [])
expect($policy['retired_gate_ids'])->toContain(
'fast-feedback-profile-contract',
'fast-feedback-local-calibration',
'fast-feedback-qualified-enforcement',
)->and($policy['reason_codes'])
->not->toHaveKey('NO_BUDGET_PROFILE_OR_CALIBRATION_SEMANTICS_CHANGED')
->and($policy['signals']['quality-gate-authority']['required_gates'] ?? [])
->not->toContain('confidence', 'heavy-governance')
->and($policy['signals']['local-finalization-authority']['required_gates'] ?? [])
->not->toContain('confidence', 'heavy-governance')
->and($policy['signals']['lane-evidence-governance']['required_gates'] ?? [])
->toContain('quality-gate-self-tests', 'foundation-regression', 'fast-feedback', 'confidence', 'heavy-governance')
->and($policy['signals']['budget-governance']['required_gates'] ?? [])
->toContain('fast-feedback-local-calibration')
->not->toContain(
'fast-feedback-profile-contract',
'fast-feedback-local-calibration',
'fast-feedback-qualified-enforcement',
)->and($routedGates)
->not->toContain(
'fast-feedback-profile-contract',
'fast-feedback-local-calibration',
'fast-feedback-qualified-enforcement',
)
->and($contract['execution_contract_classification'] ?? null)
->toBe('lane-evidence-governance')
->and(array_keys($contract['mixed_paths'] ?? []))
@ -206,5 +205,8 @@
expect(str_contains(strtolower($policyBytes), 'spec 468'))
->toBeFalse()
->and(str_contains(strtolower($policyBytes), 'spec-468'))
->toBeFalse();
->toBeFalse()
->and(collect($policy['rules'])->contains(
fn (array $rule): bool => ($rule['prefix'] ?? null) === 'specs/464-platform-fast-feedback-profile-separation-budget-governance-v1/',
))->toBeFalse();
});

View File

@ -4,7 +4,7 @@
use Symfony\Component\Process\Process;
it('proves critical safe local commit success and refusal behavior', function (): void {
it('proves the only active safe local commit contract and its refusal behavior', function (): void {
$suite = repo_path('scripts/tests/codex-safe-local-commit-test');
$helper = repo_path('scripts/codex-safe-local-commit');
@ -20,7 +20,7 @@
expect($syntax->isSuccessful())
->toBeTrue($syntax->getErrorOutput());
$process = new Process([$suite, '--critical'], repo_root(), [
$process = new Process([$suite, '--lean-control-plane'], repo_root(), [
'PATH' => getenv('PATH') ?: '/usr/bin:/bin',
'HOME' => sys_get_temp_dir(),
'GIT_CONFIG_NOSYSTEM' => '1',
@ -32,10 +32,47 @@
->toBeTrue(trim($process->getErrorOutput()) ?: trim($process->getOutput()))
->and($process->getOutput())
->toContain(
'PASS tracked modification commits exactly one authorized path',
'PASS lean mode commits one reviewed candidate without a receipt',
'PASS lean mode refuses legacy dispatch incomplete authority unsafe scope drift and conflicts',
'PASS fingerprint mode is repeatable and read-only',
'PASS stale fingerprint refuses without mutation',
'PASS hook rejection preserves real state',
'result=passed passed=4 failed=0',
'result=passed passed=5 failed=0',
);
});
it('proves the lean single-writer reference topology and remote boundary', function (): void {
$workflow = repo_path('scripts/tests/codex-agent-reference-workflow');
expect(is_file($workflow))->toBeTrue()
->and(is_executable($workflow))->toBeTrue();
$syntax = new Process(['bash', '-n', $workflow], repo_root());
$syntax->setTimeout(15);
$syntax->run();
expect($syntax->isSuccessful())->toBeTrue($syntax->getErrorOutput());
$process = new Process([$workflow], repo_root(), [
'PATH' => getenv('PATH') ?: '/usr/bin:/bin',
'HOME' => sys_get_temp_dir(),
'GIT_CONFIG_NOSYSTEM' => '1',
]);
$process->setTimeout(60);
$process->run();
expect($process->isSuccessful())
->toBeTrue(trim($process->getErrorOutput()) ?: trim($process->getOutput()))
->and($process->getOutput())
->toContain(
'PASS FAST main_agent=1 explorer=optional implementer=retired',
'PASS NORMAL main_agent=1',
'PASS CRITICAL main_agent=1',
'PASS parallel-writers-refused',
'PASS critical-validator-limit-refused',
'PASS active-control-plane-reference-check',
'PASS lean-safe-commit',
'PASS remote-authority-refused',
'result=passed suite=codex-agent-reference-workflow',
);
});

View File

@ -63,23 +63,20 @@ ## Testing Rules
- Add tenant isolation tests for tenant-owned models.
- Use PostgreSQL lane for JSONB, partial indexes, locks, composite FKs, and migrations.
- Browser tests are for critical workflows and JS/visual smoke, not every CRUD page.
- For policy-v2 candidates, resolve the routing manifest through the existing
change-impact authority and run only Registry commands marked `REQUIRED`.
`NOT_REQUIRED` is candidate-bound routing evidence, never `SKIPPED`, `PASS`, or
permission to relabel a historical result. Review topology selects independent
roles separately and never supplies executable command authority.
- Resolve test depth through the existing change-impact authority and run only
Registry commands marked `REQUIRED`. `NOT_REQUIRED` is never `SKIPPED`, `PASS`,
or permission to relabel a historical result. The Main Agent is the sole
writer; FAST/NORMAL use one final reviewer and CRITICAL may add at most one
signal-selected validator before that reviewer.
## Repository-Governance Routing Changes
## Lean Agent Control Plane
The Spec 468 implementation is a repository-governance-only change. Its narrow
complete proof is the router, quality-gate, handoff, report, receipt, safe-helper,
and foundation shell suites, one focused Pest contract guard, and Sail Pint.
Browser verification is N/A because no rendered UI or browser workflow changes;
PostgreSQL verification is N/A because there is no migration, database contract,
or database-coupled runtime behavior. Deployment impact is none: no environment,
queue, scheduler, storage, asset, container, Dokploy, staging, or production
change. No separate follow-up spec is needed for this bounded routing/evidence
cutover; dependent parked specs remain explicitly outside its scope.
FAST/NORMAL work does not require an Explorer, separate Implementer, Validator,
Git-Finalizer agent, handoff, Completion Evidence, receipt, correction contract,
calibration, or profile qualification. Freeze one Candidate fingerprint after
routed tests, obtain exactly one independent final review, and use Lean Safe
Commit only with separate explicit commit authority. CRITICAL signals may require
one validator and deeper tests. Remote operations always need separate authority.
## Security Rules

View File

@ -31,23 +31,12 @@ ## Minimum Standard for New Features
## Risk-Based Validation Routing
For policy-v2 candidates, the existing change-impact authority freezes one
manifest decision per concrete Validation Command Registry gate. Execute only
gates resolved as `REQUIRED`, with the Registry remaining the sole command
authority. Preserve a `NOT_REQUIRED` decision only as candidate-, policy-, and
manifest-bound routing evidence with its controlled reason; it is neither a
skipped run nor a successful result. `CONDITIONAL` cannot reach freeze, and no
executed red or historical lane result may be relabeled. Independent review roles
come from the separate review topology.
The Spec 468 routing-authority candidate uses repository-governance shell suites,
the foundation validator, one focused Pest guard, and Sail Pint as its narrowest
complete CRITICAL proof. The browser lane is N/A because no UI surface or browser
journey changes. The PostgreSQL lane is N/A because no migration, data model,
query, lock, constraint, or PostgreSQL behavior changes. Deployment validation is
N/A because no product runtime or deployable configuration changes. This bounded
routing/evidence cutover needs no dedicated follow-up spec; already parked
dependent specs remain separate and untouched.
The existing change-impact authority freezes one decision per Registry gate.
Execute only `REQUIRED` commands. `NOT_REQUIRED` is neither a skipped run nor a
successful result, `CONDITIONAL` cannot reach freeze, and no executed red result
may be relabelled. Risk controls test depth: FAST uses focused proof, NORMAL
normally adds Fast Feedback, and CRITICAL may select deeper lanes and one
independent validator. Every candidate still receives one final reviewer.
## Critical User Journeys to Keep Covered
@ -132,16 +121,16 @@ ## Fragility Controls
- Use fake Graph clients that fail hard if UI rendering accidentally calls Graph.
- Avoid snapshot tests for volatile admin markup unless visual regression is the real goal.
## Fast Feedback Execution Profiles and Hybrid Budgets
## Retired Fast Feedback Profile Governance
Fast Feedback uses two independent identities. `selection_identity` describes the exact selected files and contents, normalized selectors, final test/assertion/skip counts, fixtures, database mode, Xdebug, process count, runner versions, lane/wrapper/routing authorities, and required report contract. `execution_profile_identity` describes measurable provider-neutral resources such as architecture, CPU quota, RAM, container/runtime image, PHP, process count, database mode, Xdebug, and persistent versus ephemeral lifecycle. Provider, workflow, hostname, branch, worktree, and timestamp metadata never choose either identity.
Profile qualification, calibration series, budget/profile identities,
exact-candidate calibration, sidecar rehydration, and cause-normalization
forensics are retired workflow concepts. Any files that still implement them are
unsupported dead code pending Spec 473 deletion. The active agent Control Plane
must not invoke them. Git history is the historical archive.
Supported labels are `local-fast-feedback` and `ephemeral-ci-fast-feedback`. A profile is `UNQUALIFIED`, `CALIBRATING`, `QUALIFIED`, or `STALE`. Missing, unknown, unqualified, calibrating, and stale profiles fail closed during normal enforcement. Calibration Mode requires an explicit profile, series, monotonic sample ordinal, and attributable authority reference; even a functionally green measurement exits non-zero and is never a normal Required-Check pass.
The local calibration contract uses exactly the first four eligible attempts with the same two identities. Functional, infrastructure, artifact, stale-profile, cost-drift, hidden-fixture, duplicate-execution, and incomplete-report failures remain recorded but cannot contribute. The superseded `fast-feedback-budget-v1` artifact remains readable. Current `fast-feedback-budget-v2` retains its q75 Advisory, derives 15-to-30-second MAD-based headroom, and floors Hard at `ceil(maximum eligible sample) + headroom`; Hard above 400 rejects qualification rather than being clipped. A later enforcement result never changes the tuple. The 2,100-second workflow timeout remains an infrastructure timeout, never a lane budget. Do not remove tests/assertions, increase skips, change fixtures/processes, or shrink the selected files to improve runtime.
Failure interpretation uses this fixed precedence while retaining every applicable secondary class: `ARTIFACT_CONTRACT_FAILURE`, `INFRASTRUCTURE_ERROR`, `EXECUTION_PROFILE_STALE`, `EXECUTION_PROFILE_UNQUALIFIED`, `COST_CONTRACT_DRIFT`, `TEST_FAILURE`, then `BUDGET_BREACH`. Wall clock is evaluated only when profile, selection/cost, artifacts, infrastructure, and functional truth are valid. At/below Advisory passes; above Advisory through exact Hard warns; only above Hard blocks as a budget breach.
Trend/evidence v2 is additive and comparable only when contract version, lane, Selection Identity, Execution Profile Identity, formula version, and binding hash all match. V1 records remain byte-preserved, retain their original outcome, project only as `legacy-unqualified`, receive no synthetic v2 identities, and never become qualification evidence.
Spec 464 qualifies only the measured local profile. Ephemeral CI may later collect an explicitly authorized real hosted series but remains unqualified here; no workflow or Required Check is activated and no self-hosted profile exists. After Spec 464 is integrated, Spec 463 must resume at T066G with a fresh post-integration candidate, fresh review, and fresh receipt. Candidate A `cd4f2a8a116cbba1b531aba0009078df23919072` and its prior review/receipt are historical evidence only.
Until Spec 473 physically simplifies the Fast Feedback wrapper, distinguish
functional test truth from a wrapper-only Profile/COST result. Functional test
failures remain blocking; wrapper-only Profile/COST behavior is reported as
known Spec-473 debt and never authorizes calibration or qualification work in a
FAST/NORMAL flow.

View File

@ -969,20 +969,16 @@ if [[ "${mode}" == "fingerprint" ]]; then
exit 0
fi
[[ ${lean_mode} -eq 1 ]] || refuse legacy-mode-retired
[[ -n "${commit_message//[[:space:]]/}" ]] || refuse missing-message
[[ -n "${expected_fingerprint}" ]] || refuse missing-fingerprint
[[ "${expected_fingerprint}" =~ ^[0-9a-f]{64}$ ]] || refuse invalid-fingerprint
[[ "${candidate_fingerprint}" == "${expected_fingerprint}" ]] || refuse stale-fingerprint
if [[ ${lean_mode} -eq 1 ]]; then
[[ ${current_user_authority} -eq 1 ]] || refuse missing-current-user-authority
[[ -n "${review_verdict}" ]] || refuse missing-review-verdict
[[ "${review_verdict}" == "NO_CONFIRMED_FINDINGS" ]] || refuse review-not-approved
[[ -z "${receipt_argument}" ]] || refuse lean-receipt-not-allowed
[[ -z "${correction_contract_argument}" ]] || refuse lean-correction-contract-not-allowed
else
validate_finalization_receipt
validate_correction_contract
fi
[[ ${current_user_authority} -eq 1 ]] || refuse missing-current-user-authority
[[ -n "${review_verdict}" ]] || refuse missing-review-verdict
[[ "${review_verdict}" == "NO_CONFIRMED_FINDINGS" ]] || refuse review-not-approved
[[ -z "${receipt_argument}" ]] || refuse lean-receipt-not-allowed
[[ -z "${correction_contract_argument}" ]] || refuse lean-correction-contract-not-allowed
prepare_isolated_repository
@ -1000,9 +996,7 @@ assert_head_matches
assert_index_unstaged
reviewed_fingerprint="${candidate_fingerprint}"
reviewed_tree="${candidate_tree}"
if [[ ${lean_mode} -eq 1 ]]; then
assert_no_unexpected_changed_paths
fi
assert_no_unexpected_changed_paths
build_candidate
[[ "${candidate_fingerprint}" == "${reviewed_fingerprint}" ]] || refuse candidate-drift
[[ "${candidate_tree}" == "${reviewed_tree}" ]] || refuse candidate-drift
@ -1010,10 +1004,9 @@ build_candidate
publish_commit "${new_commit}"
published_commit="${new_commit}"
consume_finalization_receipt
printf 'result=committed\n'
printf 'commit_mode=%s\n' "$([[ ${lean_mode} -eq 1 ]] && printf lean || printf legacy-receipt)"
printf 'commit_mode=lean\n'
printf 'reviewed_head=%s\n' "${expected_head}"
printf 'candidate_sha256=%s\n' "${candidate_fingerprint}"
emit_paths

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,7 @@ set -euo pipefail
repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
helper="${repository_root}/scripts/codex-safe-local-commit"
mode="${1:-full}"
mode="${1:---lean-control-plane}"
passed=0
failed=0
suite_root="$(mktemp -d "${TMPDIR:-/tmp}/codex-safe-local-commit-suite.XXXXXX")"
@ -14,10 +14,10 @@ sanitized_tmp="${suite_root}/tmp"
mkdir -p "${sanitized_home}" "${sanitized_tmp}"
case "${mode}" in
full|--critical)
--lean-control-plane)
;;
*)
echo "Usage: scripts/tests/codex-safe-local-commit-test [--critical]" >&2
echo "Usage: scripts/tests/codex-safe-local-commit-test [--lean-control-plane]" >&2
exit 2
;;
esac
@ -542,6 +542,10 @@ case_lean_commit_authority_and_scope_refusals() {
sanitized_helper commit --repo "${repository}" --expected-head "${head}" \
--candidate-sha256 "${fingerprint}" --message "blocked review" --lean \
--review-verdict CONFIRMED_FINDINGS --current-user-authority --path tracked.txt
assert_refused legacy-mode-retired \
sanitized_helper commit --repo "${repository}" --expected-head "${head}" \
--candidate-sha256 "${fingerprint}" --message "legacy mode" \
--current-user-authority --path tracked.txt
spec_path="specs/459-mechanical-agent-quality-gates-validation-routing"
receipt="$(fixture_receipt "${repository}" "${head}" "${fingerprint}" "${spec_path}")"
@ -750,7 +754,7 @@ case_stale_fingerprint_refuses_without_mutation() {
before="$(repository_snapshot "${repository}")"
assert_refused stale-fingerprint \
helper_commit "${repository}" "${fingerprint}" "stale candidate" tracked.txt
helper_lean_commit "${repository}" "${fingerprint}" "stale candidate" tracked.txt
after="$(repository_snapshot "${repository}")"
[[ "${before}" == "${after}" ]]
@ -880,7 +884,7 @@ case_hook_rejection_preserves_real_state() {
before="$(repository_snapshot "${repository}")"
assert_refused hook-rejected \
helper_commit "${repository}" "${fingerprint}" "hook rejects" tracked.txt
helper_lean_commit "${repository}" "${fingerprint}" "hook rejects" tracked.txt
after="$(repository_snapshot "${repository}")"
[[ "${before}" == "${after}" ]]
@ -1198,33 +1202,11 @@ case_policy_v2_receipt_bindings_refuse_legacy_or_drift() {
helper_commit_with_receipt "${repository}" "${fingerprint}" "v2 receipt drift probe" "${receipt}" tracked.txt
}
run_case "tracked modification commits exactly one authorized path" case_tracked_success
run_case "lean mode commits one reviewed candidate without a receipt" case_lean_commit_without_receipt
run_case "lean mode refuses legacy dispatch incomplete authority unsafe scope drift and conflicts" case_lean_commit_authority_and_scope_refusals
run_case "fingerprint mode is repeatable and read-only" case_fingerprint_is_repeatable_and_read_only
run_case "stale fingerprint refuses without mutation" case_stale_fingerprint_refuses_without_mutation
run_case "hook rejection preserves real state" case_hook_rejection_preserves_real_state
if [[ "${mode}" == "full" ]]; then
run_case "lean mode commits one reviewed candidate without a receipt" case_lean_commit_without_receipt
run_case "lean mode refuses incomplete authority unsafe scope drift and conflicts" case_lean_commit_authority_and_scope_refusals
run_case "mixed path states and encodings commit safely" case_mixed_paths_and_encoding
run_case "case-only rename commits canonical tree" case_case_only_rename_commits_canonical_tree
run_case "case-only rename preserves executable and symlink modes" case_case_only_rename_preserves_modes
run_case "rename requires both paths" case_rename_requires_both_paths
run_case "stale HEAD refuses without mutation" case_stale_head_refuses_without_mutation
run_case "pre-staged index refuses without mutation" case_staged_index_refuses_without_mutation
run_case "protected detached and operation states refuse" case_protected_detached_and_operation_refusals
run_case "invalid and sensitive paths refuse" case_invalid_paths_refuse
run_case "hook-staged drift refuses publication" case_hook_staged_drift_refuses_publication
run_case "hook ref and remote config mutations stay isolated" case_hook_ref_and_remote_config_stay_isolated
run_case "commit requires complete authority" case_commit_requires_complete_authority
run_case "receipt path consumed and stale states refuse" case_receipt_path_and_state_refusals
run_case "correction contract scope mismatch refuses" case_correction_contract_scope_refuses
run_case "correction contract commits exactly once with separate authority" case_correction_contract_exact_commit
run_case "correction contract and receipt identity drift refuse" case_correction_identity_drift_refuses
run_case "signal interruption cleans disposable state" case_signal_cleanup
run_case "publication faults restore ref and index" case_publication_faults_restore_ref_and_index
run_case "policy-v2 receipts reject legacy and full-delta drift" case_policy_v2_receipt_bindings_refuse_legacy_or_drift
fi
printf 'result=%s passed=%d failed=%d\n' "$([[ ${failed} -eq 0 ]] && echo passed || echo failed)" "${passed}" "${failed}"
[[ ${failed} -eq 0 ]]

View File

@ -1,744 +1,275 @@
#!/usr/bin/env bash
set -euo pipefail
repository_root="${TENANTATLAS_REPO_ROOT:-}"
if [[ -z "${repository_root}" ]]; then
repository_root="$(git rev-parse --show-toplevel)"
fi
source "${repository_root}/scripts/tests/agent-quality-gates-test-lib"
routing_manifest_cases="scripts/tests/fixtures/agent-quality-gates/routing-manifest/cases.json"
php -r '
$fixture = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
$expected = [
"deterministic-length-prefixed-records",
"bytewise-path-ordering",
"utf8-nfc-collision",
"added-semantics",
"modified-semantics",
"deleted-semantics",
"renamed-semantics",
"old-mode-drift",
"new-mode-drift",
"raw-blob-byte-drift",
"execution-contract-routing-drift",
"execution-contract-non-routing-stability",
"derived-evidence-content-stability",
"derived-evidence-path-drift",
"persisted-manifest-rehash",
"content-addressed-filename",
"atomic-write",
"manifest-tamper",
"deterministic-terminal-blocked",
];
$actual = array_column($fixture["cases"] ?? [], "id");
if (($fixture["domain"] ?? null) !== "tenantpilot-routing-scope-v1"
|| ($fixture["manifest_domain"] ?? null) !== "tenantpilot-routing-manifest-v1"
|| $actual !== $expected) {
fwrite(STDERR, "routing-manifest-fixture-contract\n");
exit(1);
repository_root="${TENANTATLAS_REPO_ROOT:-$(git rev-parse --show-toplevel)}"
suite_root="$(mktemp -d "${TMPDIR:-/tmp}/lean-routing-suite.XXXXXX")"
passed=0
failed=0
cleanup() {
[[ -d "${suite_root}" ]] && rm -rf -- "${suite_root}"
}
' "${routing_manifest_cases}"
trap cleanup EXIT INT TERM
php -r '
$policy = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
if (($policy["policy_version"] ?? null) !== 2) {
fwrite(STDERR, "routing-policy-v1-rejected\n");
exit(1);
}
if (!is_file($argv[2])) {
fwrite(STDERR, "routing-manifest-schema-missing\n");
exit(1);
}
' .agent/quality-gates/change-impact-rules.json .agent/quality-gates/validation-routing-manifest.schema.json
new_repository() {
local risk_class="$1"
local with_contract="${2:-0}"
local repository spec
assert_policy_v2_fast_manifest() {
local repo="$1"
local case_id="$2"
local first second
repository="$(mktemp -d "${suite_root}/repository.XXXXXX")"
git -C "${repository}" init -q -b 900-lean-router-fixture
git -C "${repository}" config user.name "Lean Router Fixture"
git -C "${repository}" config user.email "lean-router@example.invalid"
mkdir -p "${repository}/.agent" "${repository}/scripts" \
"${repository}/specs/900-lean-router-fixture/checklists"
cp -R "${repository_root}/.agent/quality-gates" "${repository}/.agent/quality-gates"
cp "${repository_root}/scripts/resolve-change-validation" "${repository}/scripts/resolve-change-validation"
chmod +x "${repository}/scripts/resolve-change-validation"
first="$(
cd "${repo}"
scripts/resolve-change-validation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD \
--write-routing-manifest \
--format json
)"
second="$(
cd "${repo}"
scripts/resolve-change-validation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD \
--write-routing-manifest \
--format json
)"
spec="${repository}/specs/900-lean-router-fixture"
printf '# Feature Specification: Lean Router Fixture\n\n## Risk Classification\n\n- **Risk Class**: %s\n\n## Scope\n\nBounded fixture.\n\n## Acceptance Criteria\n\n1. Routing resolves deterministically.\n\n## Test Strategy\n\nFocused routing proof.\n' \
"${risk_class}" >"${spec}/spec.md"
printf '# Implementation Plan: Lean Router Fixture\n\n## Test Strategy\n\nFocused.\n\n## Final Review\n\nExactly one reviewer.\n' >"${spec}/plan.md"
printf '# Tasks\n\n- [ ] T001 Implement fixture.\n- [ ] T002 Final review.\n' >"${spec}/tasks.md"
printf '# Requirements\n\n- [x] Scope bounded.\n' >"${spec}/checklists/requirements.md"
php -r '
[$first, $second, $fixturePath, $registryPath, $caseId] = array_slice($argv, 1);
$left = json_decode($first, true, 512, JSON_THROW_ON_ERROR);
$right = json_decode($second, true, 512, JSON_THROW_ON_ERROR);
$fixture = json_decode(file_get_contents($fixturePath), true, 512, JSON_THROW_ON_ERROR);
$registry = json_decode(file_get_contents($registryPath), true, 512, JSON_THROW_ON_ERROR);
$cases = array_column($fixture["policy_v2"]["cases"] ?? [], null, "id");
$case = $cases[$caseId] ?? throw new RuntimeException("router-case-missing:".$caseId);
foreach (["policy_version", "risk_class", "routing_scope_sha256", "routing_manifest_path", "routing_manifest_sha256", "human_summary"] as $field) {
if (!array_key_exists($field, $left)) {
throw new RuntimeException("routing-field-missing:".$field);
}
}
if (($left["risk_class"] ?? null) !== "FAST" || ($right["risk_class"] ?? null) !== "FAST") {
throw new RuntimeException("fast-risk-class");
}
foreach (["routing_scope_sha256", "routing_manifest_sha256"] as $field) {
if (($left[$field] ?? null) !== ($right[$field] ?? null)) {
throw new RuntimeException("routing-repeat-drift:".$field);
}
}
$manifestPath = $left["routing_manifest_path"] ?? "";
if (!is_file($manifestPath) || basename($manifestPath, ".json") !== ($left["routing_manifest_sha256"] ?? null)) {
throw new RuntimeException("routing-manifest-selection");
}
$manifest = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
$requirements = $manifest["gate_requirements"] ?? [];
$gateIds = array_column($requirements, "gate_id");
$registryIds = array_keys($registry["commands"] ?? []);
sort($gateIds, SORT_STRING);
sort($registryIds, SORT_STRING);
if ($gateIds !== $registryIds || count($gateIds) !== count(array_unique($gateIds))) {
throw new RuntimeException("routing-exact-registry-coverage");
}
$byGate = array_column($requirements, null, "gate_id");
foreach ($requirements as $requirement) {
if (!in_array($requirement["requirement"] ?? null, ["REQUIRED", "NOT_REQUIRED"], true)
|| ($requirement["command_authority"] ?? null) !== "REGISTRY") {
throw new RuntimeException("routing-frozen-requirement-contract");
}
}
foreach ($case["required_gates"] ?? [] as $gateId) {
if (($byGate[$gateId]["requirement"] ?? null) !== "REQUIRED") {
throw new RuntimeException("routing-required-gate:".$gateId);
}
}
foreach ($case["not_required_gates"] ?? [] as $gateId) {
if (($byGate[$gateId]["requirement"] ?? null) !== "NOT_REQUIRED") {
throw new RuntimeException("routing-not-required-gate:".$gateId);
}
}
foreach ($case["not_required_reason"] ?? [] as $gateId => $reasonCode) {
if (($byGate[$gateId]["reason_code"] ?? null) !== $reasonCode) {
throw new RuntimeException("routing-not-required-reason:".$gateId);
}
}
$roles = $manifest["review_topology"]["required_roles"] ?? [];
if ($roles !== ["code_reviewer"]
|| ($manifest["review_topology"]["main_agent_count"] ?? null) !== 1
|| ($manifest["review_topology"]["code_reviewer_count"] ?? null) !== 1
|| ($manifest["review_topology"]["no_parallel_writers"] ?? null) !== true
|| ($manifest["review_topology"]["evidence_freeze_before_review"] ?? null) !== true) {
throw new RuntimeException("routing-fast-review-topology");
}
foreach (["risk_class", "active_signals", "required_gates", "not_required_gates", "required_review_roles", "impact_flags", "candidate_identities"] as $field) {
if (!array_key_exists($field, $left["human_summary"] ?? [])) {
throw new RuntimeException("routing-human-summary:".$field);
}
}
$encodedSummary = json_encode($left["human_summary"], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
foreach (["raw_diff", "junit", "evidence_body", "raw_payload"] as $forbidden) {
if (str_contains(strtolower($encodedSummary), $forbidden)) {
throw new RuntimeException("routing-human-summary-payload:".$forbidden);
}
}
foreach ($case["forbidden_manifest_values"] ?? [] as $forbidden) {
if (str_contains(json_encode($manifest, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), $forbidden)) {
throw new RuntimeException("routing-spec-id-exception:".$forbidden);
}
}
' "${first}" "${second}" \
"${fixture_root}/scripts/tests/fixtures/agent-quality-gates/router/cases.json" \
"${repo}/.agent/quality-gates/validation-command-registry.json" \
"${case_id}"
if [[ "${with_contract}" == "1" ]]; then
printf '%s\n' \
'{' \
' "schema_version": 1,' \
' "risk_class": "CRITICAL",' \
' "scope": {' \
' "allowed_paths": ["apps/platform/app/", ".agent/quality-gates/", "specs/900-lean-router-fixture/"],' \
' "forbidden_paths": [".gitea/workflows/", ".github/workflows/"]' \
' },' \
' "required_gates": ["change-validation", "independent-review"],' \
' "remote_boundary": {' \
' "provider": "GITEA",' \
' "remote": "origin",' \
' "target": "platform-dev",' \
' "mutations_require_separate_authority": true,' \
' "github_mutation_allowed": false' \
' }' \
'}' >"${spec}/execution-contract.json"
fi
git -C "${repository}" add .
git -C "${repository}" commit -q -m baseline
printf '%s\n' "${repository}"
}
repo="$(new_gate_repo 'docs/')"
(
cd "${repo}"
mkdir -p docs
printf '# Generic routing note\n' > docs/example-governance-note.md
)
assert_policy_v2_fast_manifest "${repo}" generic-docs-only-fast
repo="$(new_gate_repo 'apps/platform/tests/')"
(
cd "${repo}"
mkdir -p apps/platform/tests/Feature
printf '%s\n' '<?php' '' "it('routes generic tests', function (): void {" ' expect(true)->toBeTrue();' '});' > apps/platform/tests/Feature/ExampleContractTest.php
)
assert_policy_v2_fast_manifest "${repo}" generic-test-only-fast
repo="$(new_gate_repo 'apps/platform/tests/')"
(
cd "${repo}"
mkdir -p apps/platform/tests/Feature
printf '%s\n' '<?php' '' "it('uses an isolated fixture', function (): void {" ' expect(true)->toBeTrue();' '});' > apps/platform/tests/Feature/ExampleContractTest.php
git add apps/platform/tests/Feature/ExampleContractTest.php
git commit -q -m fixture-baseline
printf '%s\n' '<?php' '' 'beforeEach(function (): void {' ' $this->fixtureMode = true;' '});' '' "it('uses an isolated fixture', function (): void {" ' expect(true)->toBeTrue();' '});' > apps/platform/tests/Feature/ExampleContractTest.php
)
assert_policy_v2_fast_manifest "${repo}" generic-fixture-only-fast
assert_policy_v2_fast_manifest "${repo}" repeated-routing-determinism
assert_policy_v2_fast_manifest "${repo}" exact-registry-gate-coverage
assert_policy_v2_fast_manifest "${repo}" no-spec-id-routing
assert_policy_v2_us2_case() {
local repo="$1"
local case_id="$2"
local output status
run_case() {
local name="$1"
shift
local output_file status
output_file="$(mktemp "${suite_root}/case-output.XXXXXX")"
set +e
output="$(
cd "${repo}"
scripts/resolve-change-validation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD \
--write-routing-manifest \
--format json 2>&1
)"
(set -e; "$@") >"${output_file}" 2>&1
status=$?
set -e
php -r '
[$output, $status, $fixturePath, $caseId] = array_slice($argv, 1);
$result = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
$fixture = json_decode(file_get_contents($fixturePath), true, 512, JSON_THROW_ON_ERROR);
$cases = array_column($fixture["policy_v2"]["us2_cases"] ?? [], null, "id");
$case = $cases[$caseId] ?? throw new RuntimeException("us2-case-missing:".$caseId);
if (($result["resolver_state"] ?? null) !== $case["expected_state"]) {
throw new RuntimeException("us2-resolver-state:".$caseId);
}
if ($case["expected_state"] === "BLOCKED") {
if ((int) $status === 0
|| array_key_exists("risk_class", $result)
|| array_key_exists("routing_manifest_path", $result)
|| array_key_exists("routing_manifest_sha256", $result)
|| (($result["manifest_selection"]["persisted"] ?? false) === true)) {
throw new RuntimeException("us2-blocked-terminal:".$caseId);
}
$blocked = $result["blocked_reasons"] ?? [];
if (!in_array($case["blocked_reason"], $blocked, true)) {
throw new RuntimeException("us2-blocked-reason:".$caseId);
}
exit(0);
}
if ((int) $status !== 0 || ($result["risk_class"] ?? null) !== $case["expected_risk_class"]) {
throw new RuntimeException("us2-risk-class:".$caseId);
}
$manifestPath = $result["routing_manifest_path"] ?? "";
$manifest = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
$requirements = array_column($manifest["gate_requirements"] ?? [], null, "gate_id");
foreach ($case["required_gates"] ?? [] as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "REQUIRED") {
throw new RuntimeException("us2-required-gate:".$caseId.":".$gateId);
}
}
foreach ($case["not_required_gates"] ?? [] as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "NOT_REQUIRED") {
throw new RuntimeException("us2-not-required-gate:".$caseId.":".$gateId);
}
}
$roles = $manifest["review_topology"]["required_roles"] ?? [];
if ($roles !== ($case["required_roles"] ?? $roles)
|| count(array_filter($roles, static fn (string $role): bool => $role === "code_reviewer")) !== 1
|| ($manifest["review_topology"]["main_agent_count"] ?? null) !== 1
|| ($manifest["review_topology"]["no_parallel_writers"] ?? null) !== true
|| array_intersect($roles, ["code_explorer", "implementer", "git_finalizer"]) !== []) {
throw new RuntimeException("us2-review-topology:".$caseId);
}
if (($result["risk_class"] ?? null) === "NORMAL" && $roles !== ["code_reviewer"]) {
throw new RuntimeException("us2-normal-extra-agent:".$caseId);
}
if (($result["risk_class"] ?? null) === "CRITICAL") {
foreach (["agent-handoff", "implementation-report", "finalization-receipt"] as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "REQUIRED") {
throw new RuntimeException("us2-critical-compatibility:".$caseId.":".$gateId);
}
}
if ($roles !== ["test_validator", "code_reviewer"]) {
throw new RuntimeException("us2-critical-validator-topology:".$caseId);
}
}
foreach ($manifest["gate_requirements"] ?? [] as $requirement) {
if (!in_array($requirement["requirement"] ?? null, ["REQUIRED", "NOT_REQUIRED"], true)
|| ($requirement["command_authority"] ?? null) !== "REGISTRY") {
throw new RuntimeException("us2-gate-authority:".$caseId);
}
}
' "${output}" "${status}" \
"${fixture_root}/scripts/tests/fixtures/agent-quality-gates/router/cases.json" \
"${case_id}"
}
new_us2_file_case() {
local case_id="$1"
local allow_prefix="$2"
local path="$3"
local repo
repo="$(new_gate_repo "${allow_prefix}")"
(
cd "${repo}"
mkdir -p "$(dirname "${path}")"
printf '%s\n' '<?php' 'final class RoutingProbe {}' >"${path}"
if [[ "${case_id}" == "product-surface-critical" ]]; then
php -r '
$path = $argv[1];
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["declared_na_gates"] = array_values(array_filter(
$contract["declared_na_gates"],
static fn (array $gate): bool => ($gate["id"] ?? null) !== "browser",
));
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
fi
)
assert_policy_v2_us2_case "${repo}" "${case_id}"
}
new_us2_file_case bounded-runtime-normal apps/platform/app/ apps/platform/app/Support/RoutingProbe.php
repo="$(new_gate_repo apps/platform/)"
(
cd "${repo}"
mkdir -p apps/platform/tests/Feature apps/platform/app/Support
printf '%s\n' '<?php' "it('routes', fn () => expect(true)->toBeTrue());" > apps/platform/tests/Feature/RoutingProbeTest.php
printf '%s\n' '<?php' 'final class RoutingProbe {}' > apps/platform/app/Support/RoutingProbe.php
)
assert_policy_v2_us2_case "${repo}" mixed-test-runtime-normal
while IFS='|' read -r case_id allow_prefix path; do
new_us2_file_case "${case_id}" "${allow_prefix}" "${path}"
done <<'US2_CRITICAL_CASES'
authentication-critical|apps/platform/app/|apps/platform/app/Auth/RoutingProbe.php
authorization-critical|apps/platform/app/|apps/platform/app/Policies/RoutingProbePolicy.php
tenant-workspace-critical|apps/platform/app/|apps/platform/app/Models/Concerns/BelongsToWorkspace.php
provider-permission-critical|apps/platform/app/|apps/platform/app/Services/MicrosoftGraph/GraphPermission.php
migration-critical|apps/platform/database/migrations/|apps/platform/database/migrations/2099_01_01_000000_routing_probe.php
queue-critical|apps/platform/app/|apps/platform/app/Jobs/RoutingProbe.php
destructive-critical|apps/platform/app/|apps/platform/app/Actions/DeleteRoutingProbe.php
customer-output-critical|apps/platform/app/|apps/platform/app/Exports/RoutingProbeExport.php
product-surface-critical|apps/platform/resources/|apps/platform/resources/views/routing-probe.blade.php
delivery-remote-critical|.gitea/workflows/|.gitea/workflows/routing-probe.yml
quality-authority-critical|.agent/quality-gates/|.agent/quality-gates/routing-probe.json
receipt-authority-critical|scripts/|scripts/issue-agent-finalization-receipt
profile-budget-critical|apps/platform/tests/|apps/platform/tests/Support/TestLaneBudget.php
US2_CRITICAL_CASES
repo="$(new_gate_repo apps/platform/)"
(
cd "${repo}"
mkdir -p apps/platform/tests/Feature apps/platform/app/Auth
printf '%s\n' '<?php' 'final class RoutingProbeTest {}' > apps/platform/tests/Feature/RoutingProbeTest.php
git add apps/platform/tests/Feature/RoutingProbeTest.php
git commit -q -m rename-source
git mv apps/platform/tests/Feature/RoutingProbeTest.php apps/platform/app/Auth/RoutingProbe.php
)
assert_policy_v2_us2_case "${repo}" rename-boundary-critical
repo="$(new_gate_repo unmapped/)"
mkdir -p "${repo}/unmapped"
printf 'unknown\n' >"${repo}/unmapped/routing-probe.txt"
assert_policy_v2_us2_case "${repo}" unknown-blocked
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# contradictory\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["required_gates"][] = "browser";
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json"
assert_policy_v2_us2_case "${repo}" contradictory-blocked
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# conditional\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$policy = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$policy["gate_policy"]["confidence"]["default_requirement"] = "CONDITIONAL";
file_put_contents($path, json_encode($policy, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/.agent/quality-gates/change-impact-rules.json"
assert_policy_v2_us2_case "${repo}" conditional-blocked
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# authority\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$policy = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
unset($policy["gate_policy"]["confidence"]);
file_put_contents($path, json_encode($policy, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/.agent/quality-gates/change-impact-rules.json"
assert_policy_v2_us2_case "${repo}" missing-authority-blocked
repo="$(new_gate_repo docs/)"
assert_policy_v2_us2_case "${repo}" incomplete-candidate-blocked
repo="$(new_gate_repo apps/platform/app/)"
mkdir -p "${repo}/apps/platform/app/Auth"
printf '%s\n' '<?php' 'final class RoutingProbe {}' >"${repo}/apps/platform/app/Auth/RoutingProbe.php"
php -r '
$path = $argv[1];
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["declared_na_gates"][] = ["id" => "confidence", "reason" => "manual downgrade probe"];
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json"
assert_policy_v2_us2_case "${repo}" manual-downgrade-blocked
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# lean receipt conflict\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["required_gates"][] = "finalization-receipt";
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json"
assert_fails_with 'lean-workflow-lifecycle-gate-forbidden:finalization-receipt|resolver-blocked:contradictory-routing-input' \
bash -c 'cd "$1" && scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format text' _ "${repo}"
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# validator conflict\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$policy = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$policy["review_policy"]["FAST"]["required_roles"] = ["test_validator", "code_reviewer"];
file_put_contents($path, json_encode($policy, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/.agent/quality-gates/change-impact-rules.json"
assert_fails_with 'invalid-review-policy-roles:FAST' \
bash -c 'cd "$1" && scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format text' _ "${repo}"
repo="$(new_gate_repo apps/platform/app/)"
mkdir -p "${repo}/apps/platform/app/Support"
printf '%s\n' '<?php' 'final class RoutingProbe {}' >"${repo}/apps/platform/app/Support/RoutingProbe.php"
php -r '
$path = $argv[1];
$policy = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$policy["review_policy"]["NORMAL"]["required_roles"] = ["code_explorer", "code_reviewer"];
file_put_contents($path, json_encode($policy, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/.agent/quality-gates/change-impact-rules.json"
assert_fails_with 'invalid-review-policy-roles:NORMAL' \
bash -c 'cd "$1" && scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format text' _ "${repo}"
repo="$(new_gate_repo docs/)"
mkdir -p "${repo}/docs"
printf '# writer topology conflict\n' >"${repo}/docs/routing-probe.md"
php -r '
$path = $argv[1];
$policy = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$policy["bootstrap_topology"]["no_parallel_writers"] = false;
file_put_contents($path, json_encode($policy, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/.agent/quality-gates/change-impact-rules.json"
assert_fails_with 'invalid-bootstrap-topology' \
bash -c 'cd "$1" && scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format text' _ "${repo}"
declare_lane_evidence_governance() {
local repo="$1"
local classification="${2:-lane-evidence-governance}"
php -r '
[$path, $classification] = array_slice($argv, 1);
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["change"]["classifications"][] = $classification;
$contract["change"]["classifications"] = array_values(array_unique($contract["change"]["classifications"]));
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json" "${classification}"
}
new_manifest_precision_repo() {
local classification="${1:-lane-evidence-governance}"
local repo
repo="$(new_gate_repo apps/platform/tests/)"
declare_lane_evidence_governance "${repo}" "${classification}"
mkdir -p "${repo}/apps/platform/tests/Support"
cat >"${repo}/apps/platform/tests/Support/TestLaneManifest.php" <<'PHP'
<?php
final class TestLaneManifest
{
public static function selectionIdentity(array $inputs): array
{
return $inputs;
}
public static function budgetFormula(int $sample): int
{
return $sample + 10;
}
public static function qualificationPolicy(): string
{
return 'stable';
}
}
PHP
git -C "${repo}" add apps/platform/tests/Support/TestLaneManifest.php specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
git -C "${repo}" commit -q -m router-precision-baseline
printf '%s\n' "${repo}"
}
new_report_precision_repo() {
local repo
repo="$(new_gate_repo apps/platform/tests/)"
declare_lane_evidence_governance "${repo}"
mkdir -p "${repo}/apps/platform/tests/Support"
cat >"${repo}/apps/platform/tests/Support/TestLaneReport.php" <<'PHP'
<?php
final class TestLaneReport
{
public static function laneResultCounts(array $evidence): array
{
return ['test_count' => 1];
}
public static function finalIdentityContracts(array $evidence): array
{
return ['lanes' => ['fast-feedback']];
}
}
PHP
git -C "${repo}" add apps/platform/tests/Support/TestLaneReport.php specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
git -C "${repo}" commit -q -m router-precision-baseline
printf '%s\n' "${repo}"
}
assert_router_precision_case() {
local repo="$1"
local case_id="$2"
local first second status
set +e
first="$(
cd "${repo}"
scripts/resolve-change-validation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD \
--write-routing-manifest \
--format json 2>&1
)"
status=$?
set -e
php -r '
[$output, $status, $fixturePath, $caseId] = array_slice($argv, 1);
$result = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
$fixture = json_decode(file_get_contents($fixturePath), true, 512, JSON_THROW_ON_ERROR);
$cases = array_column($fixture["policy_v2"]["router_precision_cases"] ?? [], null, "id");
$case = $cases[$caseId] ?? throw new RuntimeException("router-precision-case-missing:".$caseId);
$expectedState = $case["expected_state"] ?? "RESOLVED";
if (($result["resolver_state"] ?? null) !== $expectedState) {
throw new RuntimeException("router-precision-state:".$caseId);
}
if ($expectedState === "BLOCKED") {
if ((int) $status === 0
|| !in_array($case["blocked_reason"], $result["blocked_reasons"] ?? [], true)
|| array_key_exists("routing_manifest_path", $result)) {
throw new RuntimeException("router-precision-blocked:".$caseId);
}
exit(0);
}
if ((int) $status !== 0 || ($result["risk_class"] ?? null) !== "CRITICAL") {
throw new RuntimeException("router-precision-risk:".$caseId);
}
$manifestPath = $result["routing_manifest_path"] ?? "";
$manifest = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
$signals = array_column($manifest["risk_signals"] ?? [], "id");
foreach ($case["required_signals"] ?? [] as $signal) {
if (!in_array($signal, $signals, true)) {
throw new RuntimeException("router-precision-signal-missing:".$caseId.":".$signal);
}
}
foreach ($case["forbidden_signals"] ?? [] as $signal) {
if (in_array($signal, $signals, true)) {
throw new RuntimeException("router-precision-signal-forbidden:".$caseId.":".$signal);
}
}
$requirements = array_column($manifest["gate_requirements"] ?? [], null, "gate_id");
$calibration = $requirements["fast-feedback-local-calibration"] ?? null;
if (($calibration["requirement"] ?? null) !== $case["expected_calibration_requirement"]
|| (($case["expected_reason_code"] ?? null) !== null
&& ($calibration["reason_code"] ?? null) !== $case["expected_reason_code"])
|| ($calibration["command_authority"] ?? null) !== "REGISTRY") {
throw new RuntimeException("router-precision-calibration:".$caseId);
}
if (in_array("lane-evidence-governance", $signals, true)) {
foreach (["quality-gate-self-tests", "foundation-regression", "fast-feedback", "confidence", "heavy-governance"] as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "REQUIRED") {
throw new RuntimeException("router-precision-evidence-gate:".$caseId.":".$gateId);
}
}
}
' "${first}" "${status}" \
"${fixture_root}/scripts/tests/fixtures/agent-quality-gates/router/cases.json" \
"${case_id}"
if [[ ${status} -eq 0 ]]; then
second="$(
cd "${repo}"
scripts/resolve-change-validation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD \
--write-routing-manifest \
--format json
)"
php -r '
[$first, $second, $caseId] = array_slice($argv, 1);
$left = json_decode($first, true, 512, JSON_THROW_ON_ERROR);
$right = json_decode($second, true, 512, JSON_THROW_ON_ERROR);
foreach (["routing_scope_sha256", "routing_manifest_sha256"] as $field) {
if (($left[$field] ?? null) !== ($right[$field] ?? null)) {
throw new RuntimeException("router-precision-determinism:".$caseId.":".$field);
}
}
' "${first}" "${second}" "${case_id}"
passed=$((passed + 1))
printf 'PASS %s\n' "${name}"
else
failed=$((failed + 1))
printf 'FAIL %s\n' "${name}" >&2
sed -n '1,160p' "${output_file}" >&2
fi
}
repo="$(new_report_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("[\x27test_count\x27 => 1]", "[\x27test_count\x27 => 2]", $value));' "${repo}/apps/platform/tests/Support/TestLaneReport.php"
assert_router_precision_case "${repo}" evidence-only-test-lane-report
assert_route() {
local repository="$1"
local expected_risk="$2"
local expected_roles="$3"
local required_gates="$4"
local not_required_gates="${5:-}"
local first second
repo="$(new_manifest_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("return \$inputs;", "return \$inputs + [\x27lane_id\x27 => \x27confidence\x27];", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" evidence-only-test-lane-manifest
first="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD \
--write-routing-manifest --format json)"
second="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD \
--write-routing-manifest --format json)"
repo="$(new_gate_repo .agent/quality-gates/)"
declare_lane_evidence_governance "${repo}"
git -C "${repo}" add specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
git -C "${repo}" commit -q -m router-precision-baseline
php -r '$path=$argv[1]; $schema=json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR); $schema["title"]="Additive lane evidence identity"; file_put_contents($path, json_encode($schema, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");' "${repo}/.agent/quality-gates/baseline-comparison.schema.json"
assert_router_precision_case "${repo}" additive-evidence-schema
repo="$(new_report_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("[\x27fast-feedback\x27]", "[\x27fast-feedback\x27, \x27confidence\x27, \x27heavy-governance\x27]", $value));' "${repo}/apps/platform/tests/Support/TestLaneReport.php"
assert_router_precision_case "${repo}" cross-lane-evidence-producer
repo="$(new_gate_repo apps/platform/tests/)"
mkdir -p "${repo}/apps/platform/tests/Support"
printf '%s\n' '<?php' 'final class TestLaneBudget { public const HARD = 400; }' >"${repo}/apps/platform/tests/Support/TestLaneBudget.php"
assert_router_precision_case "${repo}" budget-authority-file
repo="$(new_manifest_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("return \$sample + 10;", "return \$sample + 20;", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" manifest-budget-formula
repo="$(new_gate_repo specs/464-platform-fast-feedback-profile-separation-budget-governance-v1/)"
mkdir -p "${repo}/specs/464-platform-fast-feedback-profile-separation-budget-governance-v1/calibration"
printf '{"profile":"local-fast-feedback"}\n' >"${repo}/specs/464-platform-fast-feedback-profile-separation-budget-governance-v1/calibration/local-fast-feedback-v2.json"
assert_router_precision_case "${repo}" calibration-artifact
repo="$(new_manifest_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("return \x27stable\x27;", "return \x27requalify\x27;", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" manifest-qualification-policy
repo="$(new_manifest_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); $value=str_replace("return \$inputs;", "return \$inputs + [\x27lane_id\x27 => \x27confidence\x27];", $value); $value=str_replace("return \$sample + 10;", "return \$sample + 20;", $value); file_put_contents($path, $value);' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" mixed-manifest-evidence-budget
repo="$(new_manifest_precision_repo)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("final class TestLaneManifest\n{", "final class TestLaneManifest\n{\n private static bool \$unclassified = true;", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" ambiguous-manifest-hunk
repo="$(new_manifest_precision_repo)"
php -r '
$path=$argv[1];
$contract=json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["declared_na_gates"][]=["id"=>"fast-feedback-local-calibration", "reason"=>"manual precision override"];
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${repo}/specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("return \$sample + 10;", "return \$sample + 20;", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" manual-calibration-not-required
repo="$(new_manifest_precision_repo spec-468-lane-evidence-exception)"
php -r '$path=$argv[1]; $value=file_get_contents($path); file_put_contents($path, str_replace("return \$inputs;", "return \$inputs + [\x27lane_id\x27 => \x27confidence\x27];", $value));' "${repo}/apps/platform/tests/Support/TestLaneManifest.php"
assert_router_precision_case "${repo}" spec-id-exception-attempt
if git -C "${repository_root}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
scripts/resolve-change-validation --base platform-dev --format json >/tmp/resolve-change-validation.out || true
grep -q '"gate": "change-validation"' /tmp/resolve-change-validation.out
grep -q '"required_gates"' /tmp/resolve-change-validation.out
scripts/resolve-change-validation --spec specs/460-post-integration-additive-correction-contract-v1 --base platform-dev --format json >/tmp/resolve-change-validation-spec460.out || true
grep -q '"focused-platform-pest"' /tmp/resolve-change-validation-spec460.out
grep -q '"platform-pint"' /tmp/resolve-change-validation-spec460.out
grep -q '"independent-review"' /tmp/resolve-change-validation-spec460.out
fi
repo="$(new_gate_repo)"
(
cd "${repo}"
printf 'unknown\n' > unknown.txt
assert_fails_with 'unclassified:unknown.txt' scripts/resolve-change-validation --base HEAD --format text
)
repo="$(new_gate_repo)"
(
cd "${repo}"
cp -R specs/459-mechanical-agent-quality-gates-validation-routing specs/458-explicit-target-fixture
php -r '
foreach ([
"specs/459-mechanical-agent-quality-gates-validation-routing" => [459, "mechanical-agent-quality-gates-validation-routing", "459-mechanical-agent-quality-gates-validation-routing"],
"specs/458-explicit-target-fixture" => [458, "explicit-target-fixture", "458-explicit-target-fixture"],
] as $spec => [$id, $slug, $branch]) {
$path = $spec."/execution-contract.json";
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["spec"]["id"] = $id;
$contract["spec"]["slug"] = $slug;
$contract["spec"]["path"] = $spec;
$contract["spec"]["branch"] = $branch;
$contract["spec"]["status"] = "Implemented";
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
[$first, $second, $expectedRisk, $expectedRolesCsv, $requiredCsv, $notRequiredCsv] = array_slice($argv, 1);
$left = json_decode($first, true, 512, JSON_THROW_ON_ERROR);
$right = json_decode($second, true, 512, JSON_THROW_ON_ERROR);
if (($left["risk_class"] ?? null) !== $expectedRisk
|| ($left["resolver_state"] ?? null) !== "RESOLVED"
|| ($left["routing_scope_sha256"] ?? null) !== ($right["routing_scope_sha256"] ?? null)
|| ($left["routing_manifest_sha256"] ?? null) !== ($right["routing_manifest_sha256"] ?? null)) {
throw new RuntimeException("routing-resolution-or-determinism");
}
$routing = json_decode(file_get_contents(".agent/quality-gates/change-impact-rules.json"), true, 512, JSON_THROW_ON_ERROR);
array_unshift($routing["rules"], [
"prefix" => "allowed/",
"classification" => "fixture-allowed",
"gates" => ["diff-scope", "diff-safety", "unicode", "change-validation"],
"skills" => ["spec-readiness-gate"],
"risks" => ["fixture-truth"],
]);
file_put_contents(".agent/quality-gates/change-impact-rules.json", json_encode($routing, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
'
git add specs .agent/quality-gates/change-impact-rules.json
git commit -q -m explicit-routing-targets
start_head="$(git rev-parse HEAD)"
git checkout -q -b correction-routing-session
first_output="$(TENANTPILOT_AGENT_GATE_FIXTURE_MODE=1 TENANTPILOT_AGENT_GATE_FIXTURE_TIME=2026-08-01T00:00:00Z scripts/issue-additive-correction-contract \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--session-branch correction-routing-session --start-head "${start_head}" \
--integration-base HEAD --integration-target HEAD --diff-baseline HEAD \
--summary 'First explicit target.' --root-cause 'No implicit target may be loaded.' \
--path allowed/fix.txt --format text)"
first_contract="$(awk -F= '$1 == "contract" { print $2; exit }' <<<"${first_output}")"
second_output="$(TENANTPILOT_AGENT_GATE_FIXTURE_MODE=1 TENANTPILOT_AGENT_GATE_FIXTURE_TIME=2026-08-01T00:00:00Z scripts/issue-additive-correction-contract \
--spec specs/458-explicit-target-fixture \
--session-branch correction-routing-session --start-head "${start_head}" \
--integration-base HEAD --integration-target HEAD --diff-baseline HEAD \
--summary 'Second explicit target.' --root-cause 'No Spec 459 fallback may be loaded.' \
--path allowed/fix.txt --format text)"
second_contract="$(awk -F= '$1 == "contract" { print $2; exit }' <<<"${second_output}")"
mkdir -p allowed
printf 'candidate\n' > allowed/fix.txt
$manifest = json_decode(file_get_contents($left["routing_manifest_path"]), true, 512, JSON_THROW_ON_ERROR);
$roles = $expectedRolesCsv === "" ? [] : explode(",", $expectedRolesCsv);
if (($manifest["review_topology"]["main_agent_count"] ?? null) !== 1
|| ($manifest["review_topology"]["required_roles"] ?? null) !== $roles
|| ($manifest["review_topology"]["code_reviewer_count"] ?? null) !== 1
|| ($manifest["review_topology"]["no_parallel_writers"] ?? null) !== true) {
throw new RuntimeException("lean-review-topology");
}
$requirements = array_column($manifest["gate_requirements"] ?? [], null, "gate_id");
$retired = [
"additive-correction-contract", "agent-handoff",
"fast-feedback-local-calibration", "fast-feedback-profile-contract",
"fast-feedback-qualified-enforcement", "finalization-receipt",
"implementation-report",
];
foreach ($retired as $gateId) {
if (isset($requirements[$gateId])) {
throw new RuntimeException("retired-gate-routed:".$gateId);
}
}
foreach (array_filter(explode(",", $requiredCsv)) as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "REQUIRED") {
throw new RuntimeException("required-gate-missing:".$gateId);
}
}
foreach (array_filter(explode(",", $notRequiredCsv)) as $gateId) {
if (($requirements[$gateId]["requirement"] ?? null) !== "NOT_REQUIRED") {
throw new RuntimeException("unexpected-required-gate:".$gateId);
}
}
foreach ($requirements as $requirement) {
if (!in_array($requirement["requirement"] ?? null, ["REQUIRED", "NOT_REQUIRED"], true)
|| ($requirement["command_authority"] ?? null) !== "REGISTRY") {
throw new RuntimeException("unfrozen-gate-requirement");
}
}
' "${first}" "${second}" "${expected_risk}" "${expected_roles}" "${required_gates}" "${not_required_gates}"
}
first_plan="$(scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --correction-contract "${first_contract}" --format json)"
second_plan="$(scripts/resolve-change-validation --spec specs/458-explicit-target-fixture --base HEAD --correction-contract "${second_contract}" --format json)"
grep -q '"target_spec": "specs/459-mechanical-agent-quality-gates-validation-routing"' <<<"${first_plan}"
grep -q '"target_spec": "specs/458-explicit-target-fixture"' <<<"${second_plan}"
grep -q '"additive-correction-contract"' <<<"${first_plan}"
! grep -q 'specs/459-mechanical-agent-quality-gates-validation-routing' <<<"${second_plan}"
assert_fails_with 'correction-target-spec-mismatch' scripts/resolve-change-validation \
--spec specs/458-explicit-target-fixture --base HEAD \
--correction-contract "${first_contract}" --format text
)
echo "result=passed suite=resolve-change-validation"
case_fast_docs() {
local repository
repository="$(new_repository FAST)"
mkdir -p "${repository}/docs"
printf '# Lean docs\n' >"${repository}/docs/lean-note.md"
assert_route "${repository}" FAST code_reviewer 'spec-package,diff-scope,diff-safety,unicode,change-validation,git-diff-check'
}
case_fast_test() {
local repository
repository="$(new_repository FAST)"
mkdir -p "${repository}/apps/platform/tests/Feature"
printf '%s\n' '<?php' "it('routes lean', fn () => expect(true)->toBeTrue());" \
>"${repository}/apps/platform/tests/Feature/LeanRoutingProbeTest.php"
assert_route "${repository}" FAST code_reviewer 'focused-platform-pest,fast-feedback'
}
case_normal_feature() {
local repository
repository="$(new_repository NORMAL)"
mkdir -p "${repository}/apps/platform/app/Support"
printf '%s\n' '<?php' 'final class LeanRoutingProbe {}' \
>"${repository}/apps/platform/app/Support/LeanRoutingProbe.php"
assert_route "${repository}" NORMAL code_reviewer 'focused-platform-pest,fast-feedback'
}
case_critical_security() {
local repository
repository="$(new_repository CRITICAL 1)"
mkdir -p "${repository}/apps/platform/app/Auth"
printf '%s\n' '<?php' 'final class LeanSecurityRoutingProbe {}' \
>"${repository}/apps/platform/app/Auth/LeanSecurityRoutingProbe.php"
assert_route "${repository}" CRITICAL 'test_validator,code_reviewer' 'focused-platform-pest,fast-feedback,confidence'
}
case_critical_control_plane() {
local repository
repository="$(new_repository CRITICAL 1)"
printf '{"lean":true}\n' >"${repository}/.agent/quality-gates/lean-routing-probe.json"
assert_route "${repository}" CRITICAL 'test_validator,code_reviewer' \
'quality-gate-self-tests,foundation-regression,fast-feedback' \
'confidence,heavy-governance'
}
case_unknown_signal_blocks() {
local repository output status
repository="$(new_repository FAST)"
printf 'unknown\n' >"${repository}/unknown.control-plane"
set +e
output="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD --format json 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
grep -Eq '"resolver_state": "BLOCKED"|unknown-routing-signal|routing-rule-missing' <<<"${output}"
}
case_retired_gate_blocks() {
local repository contract output status
repository="$(new_repository FAST)"
mkdir -p "${repository}/docs"
printf '# Retired gate probe\n' >"${repository}/docs/retired-gate.md"
contract="${repository}/specs/900-lean-router-fixture/execution-contract.json"
printf '%s\n' \
'{"schema_version":1,"risk_class":"FAST","scope":{"allowed_paths":["docs/"],"forbidden_paths":[".gitea/workflows/"]},"required_gates":["finalization-receipt"],"remote_boundary":{"provider":"GITEA","remote":"origin","target":"platform-dev","mutations_require_separate_authority":true,"github_mutation_allowed":false}}' \
>"${contract}"
set +e
output="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD --format text 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
grep -Eq 'retired-control-plane-gate-forbidden:finalization-receipt' <<<"${output}"
}
case_manual_risk_blocks() {
local repository output status
repository="$(new_repository FAST)"
mkdir -p "${repository}/docs"
printf '# Manual risk probe\n' >"${repository}/docs/manual-risk.md"
set +e
output="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD --risk-class FAST --format text 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
grep -Eq 'manual-risk-class-forbidden' <<<"${output}"
}
case_parallel_writer_policy_blocks() {
local repository output status
repository="$(new_repository FAST)"
mkdir -p "${repository}/docs"
printf '# Topology probe\n' >"${repository}/docs/topology.md"
php -r '$p=$argv[1]; $d=json_decode(file_get_contents($p),true,512,JSON_THROW_ON_ERROR); $d["active_topology"]["no_parallel_writers"]=false; file_put_contents($p,json_encode($d,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)."\n");' \
"${repository}/.agent/quality-gates/change-impact-rules.json"
set +e
output="$(cd "${repository}" && scripts/resolve-change-validation \
--spec specs/900-lean-router-fixture --base HEAD --format text 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
grep -Eq 'invalid-active-topology' <<<"${output}"
}
case_legacy_dispatch_is_retired() {
local output repository status
repository="$(new_repository FAST)"
set +e
output="$(cd "${repository}" && php .agent/quality-gates/lib/agent-quality-gates.php \
validate-agent-handoff --file /tmp/retired-handoff.json 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
grep -Eq 'unknown-command' <<<"${output}"
}
run_case 'FAST docs route the Lean topology' case_fast_docs
run_case 'FAST tests route focused proof and one reviewer' case_fast_test
run_case 'NORMAL feature routes Fast Feedback and one reviewer' case_normal_feature
run_case 'CRITICAL security routes one validator and one reviewer' case_critical_security
run_case 'CRITICAL control-plane change routes deep governance proof without legacy gates' case_critical_control_plane
run_case 'unknown signals fail closed' case_unknown_signal_blocks
run_case 'retired lifecycle gates cannot re-enter routing' case_retired_gate_blocks
run_case 'manual risk classification is forbidden' case_manual_risk_blocks
run_case 'parallel writer topology fails closed' case_parallel_writer_policy_blocks
run_case 'legacy command dispatch is retired' case_legacy_dispatch_is_retired
printf 'result=%s suite=resolve-change-validation passed=%d failed=%d\n' \
"$([[ ${failed} -eq 0 ]] && printf passed || printf failed)" "${passed}" "${failed}"
[[ ${failed} -eq 0 ]]

View File

@ -1,577 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
repository_root="${TENANTATLAS_REPO_ROOT:-}"
if [[ -z "${repository_root}" ]]; then
repository_root="$(git rev-parse --show-toplevel)"
fi
source "${repository_root}/scripts/tests/agent-quality-gates-test-lib"
TENANTATLAS_REPO_ROOT="${repository_root}" php -r '
require getenv("TENANTATLAS_REPO_ROOT")."/apps/platform/vendor/autoload.php";
$binding = ["advisory_seconds" => 100, "grace_seconds" => 15, "hard_seconds" => 115];
$base = [
"artifacts" => ["valid" => true, "reasons" => []],
"infrastructure" => ["valid" => true, "reasons" => []],
"profile" => ["failure_classes" => [], "sample_eligible" => false],
"cost" => ["valid" => true, "drift_reasons" => []],
"tests_passed" => true,
"wall_clock_seconds" => 90.0,
"budget_binding" => $binding,
"calibration_mode" => false,
];
$cases = [
"ARTIFACT_CONTRACT_FAILURE" => ["artifacts" => ["valid" => false, "reasons" => ["REQUIRED_REPORT_MISSING"]]],
"INFRASTRUCTURE_ERROR" => ["infrastructure" => ["valid" => false, "reasons" => ["INFRASTRUCTURE_TIMEOUT"]]],
"EXECUTION_PROFILE_STALE" => ["profile" => ["failure_classes" => ["EXECUTION_PROFILE_STALE"], "sample_eligible" => false]],
"EXECUTION_PROFILE_UNQUALIFIED" => ["profile" => ["failure_classes" => ["EXECUTION_PROFILE_UNQUALIFIED"], "sample_eligible" => false], "budget_binding" => null],
"COST_CONTRACT_DRIFT" => ["cost" => ["valid" => false, "drift_reasons" => ["TEST_COUNT_SHRANK"]]],
"TEST_FAILURE" => ["tests_passed" => false],
"BUDGET_BREACH" => ["wall_clock_seconds" => 115.000001],
];
foreach ($cases as $class => $override) {
$result = Tests\Support\TestLaneReport::evaluateFastFeedbackGovernance(array_replace($base, $override));
if ($result["failure_classes"] !== [$class] || $result["primary_failure_class"] !== $class || $result["exit_code"] !== 1 || $result["sample_eligible"] !== false) {
fwrite(STDERR, "spec464-result-class-selftest-failed:{$class}\n");
exit(1);
}
}
$collision = Tests\Support\TestLaneReport::evaluateFastFeedbackGovernance(array_replace($base, [
"artifacts" => ["valid" => false, "reasons" => ["HASH_MISMATCH"]],
"infrastructure" => ["valid" => false, "reasons" => ["INFRASTRUCTURE_TIMEOUT"]],
"cost" => ["valid" => false, "drift_reasons" => ["TEST_COUNT_SHRANK"]],
"tests_passed" => false,
]));
if ($collision["failure_classes"] !== ["ARTIFACT_CONTRACT_FAILURE", "INFRASTRUCTURE_ERROR", "COST_CONTRACT_DRIFT", "TEST_FAILURE"]
|| $collision["primary_failure_class"] !== "ARTIFACT_CONTRACT_FAILURE") {
fwrite(STDERR, "spec464-precedence-collision-selftest-failed\n");
exit(1);
}
'
repository_root="${TENANTATLAS_REPO_ROOT:-$(git rev-parse --show-toplevel)}"
audit_repo="$(new_gate_repo "allowed/" "Implemented")"
(
cd "${audit_repo}"
spec="specs/459-mechanical-agent-quality-gates-validation-routing"
scripts/run-agent-quality-gates audit --spec "${spec}" --format text | rg -q 'result=passed gate=spec-package violations=0'
assert_fails_with 'missing-spec' scripts/run-agent-quality-gates preflight --format text
)
repo="$(new_gate_repo)"
(
cd "${repo}"
php -r '
$routing = json_decode(file_get_contents(".agent/quality-gates/change-impact-rules.json"), true, 512, JSON_THROW_ON_ERROR);
array_unshift($routing["rules"], [
"prefix" => "allowed/",
"classification" => "fixture-allowed",
"gates" => ["diff-scope", "diff-safety", "unicode", "change-validation"],
"skills" => ["spec-readiness-gate"],
"risks" => ["fixture-truth"],
"signal_ids" => ["docs-only"],
]);
file_put_contents(".agent/quality-gates/change-impact-rules.json", json_encode($routing, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$postgres = json_decode(file_get_contents(".agent/quality-gates/postgresql-discovery-policy.json"), true, 512, JSON_THROW_ON_ERROR);
$postgres["zero_tests"] = "allow";
$postgres["inspected"] = [];
file_put_contents(".agent/quality-gates/postgresql-discovery-policy.json", json_encode($postgres, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
'
git add .agent/quality-gates/change-impact-rules.json .agent/quality-gates/postgresql-discovery-policy.json
git commit -q -m fixture-active-gate-policy
scripts/run-agent-quality-gates preflight --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text | rg -q 'result=passed'
mkdir -p allowed
printf 'allowed fixture change\n' > allowed/candidate.txt
scripts/run-agent-quality-gates candidate --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format json | rg -q '"gate": "change-validation"'
printf 'unknown\n' > unknown.txt
assert_fails_with 'out-of-scope:unknown.txt' scripts/run-agent-quality-gates candidate --spec specs/459-mechanical-agent-quality-gates-validation-routing --base HEAD --format text
)
repo="$(new_gate_repo)"
(
cd "${repo}"
php -r '
$contractPath = "specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json";
$contract = json_decode(file_get_contents($contractPath), true, 512, JSON_THROW_ON_ERROR);
$contract["spec"]["status"] = "Implemented";
file_put_contents($contractPath, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$routing = json_decode(file_get_contents(".agent/quality-gates/change-impact-rules.json"), true, 512, JSON_THROW_ON_ERROR);
array_unshift($routing["rules"], [
"prefix" => "allowed/",
"classification" => "fixture-allowed",
"gates" => ["diff-scope", "diff-safety", "unicode", "change-validation"],
"skills" => ["spec-readiness-gate"],
"risks" => ["fixture-truth"],
"signal_ids" => ["docs-only"],
]);
file_put_contents(".agent/quality-gates/change-impact-rules.json", json_encode($routing, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$postgres = json_decode(file_get_contents(".agent/quality-gates/postgresql-discovery-policy.json"), true, 512, JSON_THROW_ON_ERROR);
$postgres["zero_tests"] = "allow";
$postgres["inspected"] = [];
file_put_contents(".agent/quality-gates/postgresql-discovery-policy.json", json_encode($postgres, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
'
git add specs .agent/quality-gates/change-impact-rules.json .agent/quality-gates/postgresql-discovery-policy.json
git commit -q -m correction-candidate-policy
start_head="$(git rev-parse HEAD)"
git checkout -q -b correction-runner-session
issue_output="$(TENANTPILOT_AGENT_GATE_FIXTURE_MODE=1 TENANTPILOT_AGENT_GATE_FIXTURE_TIME=2026-08-01T00:00:00Z scripts/issue-additive-correction-contract \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--session-branch correction-runner-session --start-head "${start_head}" \
--integration-base HEAD --integration-target HEAD --diff-baseline HEAD \
--summary 'TOKEN=raw-prompt-canary must stay out of diagnostics.' \
--root-cause 'SECRET=diagnostic-canary must stay inside contract bytes.' \
--path allowed/fix.txt \
--path specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md \
--format text)"
correction_contract="$(awk -F= '$1 == "contract" { print $2; exit }' <<<"${issue_output}")"
mkdir -p allowed
printf 'candidate\n' > allowed/fix.txt
printf '# Pending report\n' > specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md
candidate_output="$(scripts/run-agent-quality-gates candidate \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD --correction-contract "${correction_contract}" --format json)"
rg -q '"contract_type": "ADDITIVE_CORRECTION"' <<<"${candidate_output}"
rg -q '"mode": "additive-post-integration"' <<<"${candidate_output}"
rg -q '"target_spec": "specs/459-mechanical-agent-quality-gates-validation-routing"' <<<"${candidate_output}"
rg -q '"contract_sha256": "[0-9a-f]{64}"' <<<"${candidate_output}"
! rg -q 'raw-prompt-canary|diagnostic-canary' <<<"${candidate_output}"
# baseline-comparison remains owned by the pending Spec 473 test-lane cleanup;
# it is deliberately not part of this active Control-Plane selftest.
plan_file="$(mktemp "${TMPDIR:-/tmp}/correction-runner-plan.XXXXXX")"
scripts/resolve-change-validation --spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD --correction-contract "${correction_contract}" --format json > "${plan_file}"
php -r '
$plan = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
$report = $argv[2];
$correction = $plan["correction_contract"];
$gates = [];
foreach ($plan["required_gates"] as $gate) {
$gates[] = ["gate_id" => $gate, "command" => "fixture ".$gate, "exit_code" => 0, "verdict" => "PASS", "duration_seconds" => 0.01, "artifacts" => []];
}
$evidence = [
"schema_version" => 1,
"test_governance_result" => "document-in-feature",
"spec" => ["path" => $correction["target_spec"], "id" => $correction["target_spec_id"], "status" => $correction["target_status"]],
"repository" => ["branch" => $correction["session_branch"], "starting_head" => $correction["authorized_start_head"], "base" => $correction["integration_base"], "target" => $correction["integration_target"], "baseline" => $correction["diff_baseline"]],
"changed_files" => ["additions" => 2, "modifications" => 0, "deletions" => 0, "renames" => 0, "paths" => $correction["paths"]],
"gates" => $gates,
"impacts" => ["product_surface" => "none", "browser" => "N/A - no rendered UI surface changed", "migration" => "none", "deployment" => "none", "provider_runtime" => "none", "queue_runtime" => "none", "customer_output" => "none", "postgresql" => "none"],
"review_history" => [],
"git" => ["staged" => false, "dirty" => true, "commit" => "not performed", "push" => "not performed", "pull_request" => "not performed", "merge" => "not performed", "deployment" => "not performed"],
"external_conditions" => ["network" => "not used", "remote_git" => "not used", "deployment" => "not used", "browser" => "not used"],
"router" => ["required_gates" => $plan["required_gates"], "skills" => $plan["skills"], "risks" => $plan["risks"], "manual_gates" => $plan["manual_gates"], "command_registry" => $plan["command_registry"]],
"correction" => $correction,
"additive_correction_cycles" => [[
"contract_sha256" => $correction["contract_sha256"], "target_spec" => $correction["target_spec"],
"scope_sha256" => $correction["scope_sha256"], "paths" => $correction["paths"],
"tests" => ["scripts/tests/run-agent-quality-gates-test"],
"browser" => "N/A - no rendered UI surface changed", "runtime_impact" => "none",
"correction_authority" => "confirmed before issuance", "commit_authority" => "not granted", "preserved_history" => true,
]],
];
$content = "# Implementation Report\n\n## Candidate Identity\nFixture.\n\n## Git State\nFixture.\n\n## Scope Reconciliation\nFixture.\n\n## Changed Files\nFixture.\n\n## Implementation Summary\nFixture.\n\n## PostgreSQL Inventory\nN/A.\n\n## Validation Results\nFixture.\n\n## Blockers\nNone.\n\n## Impact Decisions\nN/A.\n\n## Evidence\n\n```json tenantpilot-implementation-evidence\n".json_encode($evidence, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n```\n";
file_put_contents($report, $content);
' "${plan_file}" specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md
run() {
local name="$1"
shift
completion_output="$(scripts/run-agent-quality-gates completion \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD --correction-contract "${correction_contract}" --format json)"
! rg -q 'raw-prompt-canary|diagnostic-canary' <<<"${completion_output}"
printf 'RUN %s\n' "${name}"
(cd "${repository_root}" && "$@")
printf 'PASS %s\n' "${name}"
}
cp specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md "${plan_file}.report"
php -r '
$path=$argv[1]; $content=file_get_contents($path);
preg_match("/```json tenantpilot-implementation-evidence\n(\{[\s\S]*?\})\n```/", $content, $matches);
$e=json_decode($matches[1], true); $e["additive_correction_cycles"][]=$e["additive_correction_cycles"][0];
$block="```json tenantpilot-implementation-evidence\n".json_encode($e, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n```";
file_put_contents($path, preg_replace("/```json tenantpilot-implementation-evidence\n\{[\s\S]*?\}\n```/", $block, $content));
' specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md
set +e
multiple_cycle_output="$(scripts/run-agent-quality-gates completion \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD --correction-contract "${correction_contract}" --format text 2>&1)"
multiple_cycle_status=$?
set -e
[[ ${multiple_cycle_status} -ne 0 ]]
rg -q 'maxItems|correction-cycle-count' <<<"${multiple_cycle_output}"
run spec-package scripts/tests/validate-spec-package-test
run risk-routing scripts/tests/resolve-change-validation-test
run reference-workflow scripts/tests/codex-agent-reference-workflow
run safe-commit scripts/tests/codex-safe-local-commit-test --lean-control-plane
run foundation scripts/validate-codex-agent-foundation
cp "${plan_file}.report" specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md
php -r '
$path=$argv[1]; $content=file_get_contents($path);
preg_match("/```json tenantpilot-implementation-evidence\n(\{[\s\S]*?\})\n```/", $content, $matches);
$e=json_decode($matches[1], true); $e["correction"]["scope_sha256"]=str_repeat("0", 64);
$block="```json tenantpilot-implementation-evidence\n".json_encode($e, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n```";
file_put_contents($path, preg_replace("/```json tenantpilot-implementation-evidence\n\{[\s\S]*?\}\n```/", $block, $content));
' specs/459-mechanical-agent-quality-gates-validation-routing/implementation-report.md
set +e
mismatch_output="$(scripts/run-agent-quality-gates completion \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--base HEAD --correction-contract "${correction_contract}" --format text 2>&1)"
mismatch_status=$?
set -e
[[ ${mismatch_status} -ne 0 ]]
rg -q 'correction-mismatch:scope_sha256' <<<"${mismatch_output}"
! rg -q 'raw-prompt-canary|diagnostic-canary' <<<"${mismatch_output}"
)
php -r '
$router = json_decode(file_get_contents("scripts/tests/fixtures/agent-quality-gates/router/cases.json"), true, 512, JSON_THROW_ON_ERROR);
foreach (["fast_feedback_profile_contract", "fast_feedback_local_calibration", "fast_feedback_shared_lane_no_drift", "fast_feedback_no_ui", "fast_feedback_remote_workflow_denied"] as $case) {
if (! isset($router[$case])) { fwrite(STDERR, "missing-router-fixture:{$case}\n"); exit(1); }
}
$reports = json_decode(file_get_contents("scripts/tests/fixtures/agent-quality-gates/reports/cases.json"), true, 512, JSON_THROW_ON_ERROR);
foreach (["spec464_valid", "spec464_selection_identity_missing", "spec464_execution_profile_identity_missing", "spec464_series_hash_missing", "spec464_formula_values_invalid", "spec464_profile_status_invalid", "spec464_cost_contract_incomplete", "spec464_command_exit_false_pass", "spec464_no_impact_mismatch", "spec464_topology_invalid"] as $case) {
if (! isset($reports[$case])) { fwrite(STDERR, "missing-report-fixture:{$case}\n"); exit(1); }
}
$registry = json_decode(file_get_contents(".agent/quality-gates/validation-command-registry.json"), true, 512, JSON_THROW_ON_ERROR);
foreach (["fast-feedback-profile-contract", "fast-feedback-local-calibration", "fast-feedback-qualified-enforcement", "confidence", "heavy-governance", "postgresql-lane", "profiling", "platform-pint", "git-diff-check", "finalization-receipt"] as $command) {
if (! isset($registry["commands"][$command])) { fwrite(STDERR, "missing-spec464-command:{$command}\n"); exit(1); }
}
if (! isset($registry["manual_gates"]["independent-review"])) { fwrite(STDERR, "missing-independent-review-gate\n"); exit(1); }
$routing = json_decode(file_get_contents(".agent/quality-gates/change-impact-rules.json"), true, 512, JSON_THROW_ON_ERROR);
$byPath = [];
foreach ($routing["rules"] as $rule) {
if (isset($rule["path"])) { $byPath[$rule["path"]] = $rule; }
}
foreach (["scripts/platform-test-lane", "scripts/platform-test-report", "apps/platform/tests/Support/TestLaneManifest.php", "apps/platform/tests/Support/TestLaneBudget.php", "apps/platform/tests/Support/TestLaneReport.php"] as $path) {
foreach (["fast-feedback-profile-contract", "fast-feedback-local-calibration"] as $gate) {
if (! in_array($gate, $byPath[$path]["gates"] ?? [], true)) { fwrite(STDERR, "missing-spec464-route:{$path}:{$gate}\n"); exit(1); }
}
}
'
repo="$(new_gate_repo)"
(
cd "${repo}"
rm -rf specs/459-mechanical-agent-quality-gates-validation-routing
spec="specs/464-platform-fast-feedback-profile-separation-budget-governance-v1"
mkdir -p "${spec}/checklists" apps/platform/tests/Support
cp "${fixture_root}/${spec}/execution-contract.json" "${spec}/execution-contract.json"
php -r '
$path = $argv[1];
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["declared_na_gates"] = [];
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${spec}/execution-contract.json"
printf '# Feature Specification: Fixture\n\n## Scenarios\n\nFixture.\n' > "${spec}/spec.md"
printf '# Implementation Plan: Fixture\n\nFixture.\n' > "${spec}/plan.md"
printf '# Research\n\nFixture.\n' > "${spec}/research.md"
printf '# Quickstart\n\nFixture.\n' > "${spec}/quickstart.md"
printf '# Tasks\n\n- [x] T001 Fixture.\n' > "${spec}/tasks.md"
printf '# Checklist\n\n- [x] Fixture.\n' > "${spec}/checklists/requirements.md"
printf '# Pending report\n' > "${spec}/implementation-report.md"
git add .
git commit -q -m spec464-fixture-base
git branch -M 464-platform-fast-feedback-profile-separation-budget-governance-v1
printf '<?php // profile-contract fixture\n' > apps/platform/tests/Support/TestLaneManifest.php
printf '# Pending changed report\n' > "${spec}/implementation-report.md"
plan_file="$(mktemp "${TMPDIR:-/tmp}/spec464-plan.XXXXXX")"
scripts/resolve-change-validation --spec "${spec}" --base HEAD --format json > "${plan_file}"
report_dir="$(mktemp -d "${TMPDIR:-/tmp}/spec464-reports.XXXXXX")"
php -r '
$plan = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
$reportDir = $argv[2];
$spec = $argv[3];
$head = trim(shell_exec("git rev-parse HEAD"));
$branch = trim(shell_exec("git branch --show-current"));
$identity = str_repeat("a", 64);
$profileIdentity = str_repeat("b", 64);
$commands = [];
foreach ($plan["required_gates"] as $gate) {
$commands[] = ["gate_id" => $gate, "command" => "fixture ".$gate, "exit_code" => 0, "verdict" => "PASS", "duration_seconds" => 0.01, "artifacts" => []];
}
$samples = [];
foreach ([10.0, 11.0, 12.0, 13.0] as $index => $wall) {
$samples[] = ["ordinal" => $index + 1, "selection_identity" => $identity, "execution_profile_identity" => $profileIdentity, "test_count" => 3118, "assertion_count" => 22587, "skipped_count" => 0, "wall_clock_seconds" => $wall, "functional_result" => "PASS", "functional_exit_code" => 0, "process_exit_code" => 1, "eligible" => true];
}
$candidate = ["branch" => $branch, "head" => $head, "diff_baseline" => "HEAD", "diff_sha256" => str_repeat("c", 64)];
$evidence = [
"schema_version" => 1,
"spec" => ["path" => $spec, "id" => 464, "status" => "Ready for implementation"],
"repository" => ["branch" => $branch, "starting_head" => $head, "base" => "platform-dev", "target" => "platform-dev", "baseline" => "HEAD"],
"changed_files" => ["additions" => 1, "modifications" => 1, "deletions" => 0, "renames" => 0, "paths" => ["apps/platform/tests/Support/TestLaneManifest.php", $spec."/implementation-report.md"]],
"gates" => $commands,
"impacts" => ["product_surface" => "none", "browser" => "N/A - no rendered UI surface changed", "migration" => "none", "deployment" => "none", "provider_runtime" => "none", "queue_runtime" => "none", "customer_output" => "none", "postgresql" => "none"],
"review_history" => [],
"git" => ["staged" => false, "dirty" => true, "commit" => "not performed", "push" => "not performed", "pull_request" => "not performed", "merge" => "not performed", "deployment" => "not performed"],
"external_conditions" => ["network" => "not used", "remote_git" => "not used", "deployment" => "not used", "browser" => "not used"],
"router" => ["required_gates" => $plan["required_gates"], "skills" => $plan["skills"], "risks" => $plan["risks"], "manual_gates" => $plan["manual_gates"], "command_registry" => $plan["command_registry"]],
"test_governance_result" => "document-in-feature",
"fast_feedback_governance" => [
"candidate" => $candidate,
"selection_identity" => $identity,
"execution_profile_identity" => $profileIdentity,
"local_profile" => ["label" => "local-fast-feedback", "status" => "QUALIFIED", "selection_identity" => $identity, "execution_profile_identity" => $profileIdentity],
"calibration" => ["authority_reference" => "spec-464-current-user", "series_id" => "spec464-local-fast-feedback-v2", "series_sha256" => str_repeat("d", 64), "formula_version" => "fast-feedback-budget-v2", "required_sample_count" => 4, "samples" => $samples, "median_seconds" => 11.5, "q75_seconds" => 12.25, "mad_seconds" => 1.0, "maximum_seconds" => 13.0, "quantile_advisory_seconds" => 13, "maximum_floor_seconds" => 13, "advisory_seconds" => 13, "headroom_seconds" => 15, "grace_seconds" => 15, "hard_seconds" => 28, "hard_ceiling_seconds" => 400, "qualification_verdict" => "QUALIFIED"],
"cost_contract" => ["selection_identity_matches" => true, "execution_profile_identity_matches" => true, "selected_files_match" => true, "test_count" => 3118, "assertion_count" => 22587, "skipped_count" => 0, "fixture_contract_matches" => true, "database_strategy" => "sqlite-memory", "xdebug_mode" => "off", "process_count" => 12, "artifact_schema_version" => 2, "trend_contract_version" => "2.0.0", "hidden_fixture_cost" => false, "duplicate_execution" => false, "unexpected_skip_growth" => false, "required_reports_complete" => true],
"failure_classes" => ["ARTIFACT_CONTRACT_FAILURE", "INFRASTRUCTURE_ERROR", "EXECUTION_PROFILE_STALE", "EXECUTION_PROFILE_UNQUALIFIED", "COST_CONTRACT_DRIFT", "TEST_FAILURE", "BUDGET_BREACH"],
"artifact_contract" => ["evidence_schema_version" => 2, "trend_contract_version" => "2.0.0", "legacy_state" => "legacy-unqualified", "historical_outcomes_preserved" => true, "incompatible_trends_separated" => true],
"ephemeral_ci" => ["label" => "ephemeral-ci-fast-feedback", "status" => "UNQUALIFIED", "calibration_mode_implemented" => true, "qualified" => false, "required_check_activated" => false, "self_hosted_profile_present" => false],
"spec463_resumption" => ["protected_worktree_unchanged" => true, "candidate_a" => "cd4f2a8a116cbba1b531aba0009078df23919072", "candidate_a_role" => "historical-only", "current_authority" => false, "fresh_candidate_required" => true, "fresh_review_required" => true, "fresh_receipt_required" => true, "stale_receipt_accepted" => false, "t066g_resume_only_after_integration" => true, "remote_mutations" => false],
"confidence_no_drift" => ["selection" => true, "budget_seconds" => 450, "baseline" => true, "status" => true, "required_check" => true, "remediation" => true],
"scope_no_drift" => ["spec463" => true, "workflows" => true, "remote_state" => true, "product_runtime" => true, "ui" => true, "database" => true, "deployment" => true],
"topology" => ["classification" => "spec-464-single-main-agent-fast-feedback-governance", "main_agent_count" => 1, "implementation_subagent_count" => 0, "reviewer_count" => 1, "reviewer_role" => "code_reviewer", "evidence_frozen_before_review" => true, "remediation_cycles" => 0],
"no_impact" => ["product_surface" => "none", "browser" => "N/A - no rendered UI surface changed", "livewire" => "unchanged", "provider_registration" => "unchanged", "global_search" => "unchanged", "destructive_actions" => "none", "assets" => "none", "postgresql" => "none", "deployment" => "none", "human_product_sanity" => "N/A - no product surface changed"],
],
];
$render = static function (string $path, array $value): void {
$content = "# Implementation Report\n\n## Candidate Identity\nFixture.\n\n## Git State\nFixture.\n\n## Scope Reconciliation\nFixture.\n\n## Changed Files\nFixture.\n\n## Implementation Summary\nFixture.\n\n## PostgreSQL Inventory\nN/A.\n\n## Validation Results\nFixture.\n\n## Blockers\nNone.\n\n## Impact Decisions\nN/A.\n\n## Evidence\n\n```json tenantpilot-implementation-evidence\n".json_encode($value, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n```\n";
file_put_contents($path, $content);
};
$render($reportDir."/valid.md", $evidence);
$variants = [];
$variants["selection-missing"] = $evidence; unset($variants["selection-missing"]["fast_feedback_governance"]["selection_identity"]);
$variants["profile-missing"] = $evidence; unset($variants["profile-missing"]["fast_feedback_governance"]["execution_profile_identity"]);
$variants["series-missing"] = $evidence; unset($variants["series-missing"]["fast_feedback_governance"]["calibration"]["series_sha256"]);
$variants["formula-invalid"] = $evidence; $variants["formula-invalid"]["fast_feedback_governance"]["calibration"]["hard_seconds"] = 29;
$variants["maximum-floor-invalid"] = $evidence; $variants["maximum-floor-invalid"]["fast_feedback_governance"]["calibration"]["maximum_floor_seconds"] = 12;
$variants["profile-status-invalid"] = $evidence; $variants["profile-status-invalid"]["fast_feedback_governance"]["local_profile"]["status"] = "STALE";
$variants["cost-incomplete"] = $evidence; unset($variants["cost-incomplete"]["fast_feedback_governance"]["cost_contract"]["required_reports_complete"]);
$variants["false-pass"] = $evidence; $variants["false-pass"]["gates"][0]["exit_code"] = 1;
$variants["impact-mismatch"] = $evidence; $variants["impact-mismatch"]["impacts"]["product_surface"] = "changed";
$variants["topology-invalid"] = $evidence; $variants["topology-invalid"]["fast_feedback_governance"]["topology"]["main_agent_count"] = 2;
$variants["calibration-false-green"] = $evidence; $variants["calibration-false-green"]["fast_feedback_governance"]["calibration"]["samples"][0]["process_exit_code"] = 0;
foreach ($variants as $name => $variant) { $render($reportDir."/".$name.".md", $variant); }
' "${plan_file}" "${report_dir}" "${spec}"
cp "${report_dir}/valid.md" "${spec}/implementation-report.md"
scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/valid.md" --format text | rg -q 'result=passed'
assert_fails_with 'selection_identity:required' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/selection-missing.md" --format text
assert_fails_with 'execution_profile_identity:required' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/profile-missing.md" --format text
assert_fails_with 'series_sha256:required' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/series-missing.md" --format text
assert_fails_with 'spec464-budget-formula-values' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/formula-invalid.md" --format text
assert_fails_with 'spec464-budget-formula-values' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/maximum-floor-invalid.md" --format text
assert_fails_with 'spec464-local-profile-not-qualified' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/profile-status-invalid.md" --format text
assert_fails_with 'required_reports_complete:required' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/cost-incomplete.md" --format text
assert_fails_with 'false-pass:0' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/false-pass.md" --format text
assert_fails_with 'spec464-impact-mismatch:product_surface' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/impact-mismatch.md" --format text
assert_fails_with 'spec464-topology-contract' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/topology-invalid.md" --format text
assert_fails_with 'process_exit_code:const' scripts/validate-implementation-report --spec "${spec}" --base HEAD --report "${report_dir}/calibration-false-green.md" --format text
candidate_output="$(scripts/codex-safe-local-commit fingerprint --repo "${repo}" --expected-head "$(git rev-parse HEAD)" --path apps/platform/tests/Support/TestLaneManifest.php --path "${spec}/implementation-report.md")"
candidate_sha="$(awk -F= '$1 == "candidate_sha256" { print $2; exit }' <<<"${candidate_output}")"
handoff_dir="$(mktemp -d "${TMPDIR:-/tmp}/spec464-handoffs.XXXXXX")"
php -r '
$spec = $argv[1]; $candidateSha = $argv[2]; $dir = $argv[3];
$candidate = ["branch" => trim(shell_exec("git branch --show-current")), "head" => trim(shell_exec("git rev-parse HEAD")), "diff_baseline" => "HEAD", "diff_sha256" => $candidateSha];
$base = ["schema_version" => 1, "handoff_type" => "IMPLEMENTATION_HANDOFF", "spec" => ["path" => $spec, "id" => 464, "status" => "Ready for implementation"], "candidate" => $candidate, "scope" => ["apps/platform/tests/Support/TestLaneManifest.php", $spec."/implementation-report.md"], "contracts" => ["spec-464-single-main-agent-fast-feedback-governance"], "activated_skills" => ["spec-readiness-gate", "pest-testing"], "commands" => [["gate_id" => "fast-feedback-profile-contract", "command" => "fixture", "exit_code" => 0, "verdict" => "PASS", "candidate_diff_sha256" => $candidateSha, "duration_seconds" => 0.01, "artifacts" => []]], "failures" => [], "findings" => [], "hypotheses" => [], "uncertainties" => [], "dirty_state" => "exact candidate paths only; index empty", "next_agent" => "code_reviewer", "verdict" => "PASS"];
file_put_contents($dir."/implementation.json", json_encode($base, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$invalid = $base; $invalid["handoff_type"] = "VALIDATION_HANDOFF";
file_put_contents($dir."/validation-forbidden.json", json_encode($invalid, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
$review = $base; $review["handoff_type"] = "REVIEW_RESULT"; $review["commands"] = []; $review["next_agent"] = "none"; $review["verdict"] = "NO_CONFIRMED_FINDINGS"; $review["test_governance_result"] = "document-in-feature";
file_put_contents($dir."/review.json", json_encode($review, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
' "${spec}" "${candidate_sha}" "${handoff_dir}"
scripts/validate-agent-handoff --file "${handoff_dir}/implementation.json" --format text | rg -q 'result=passed'
scripts/validate-agent-handoff --file "${handoff_dir}/review.json" --format text | rg -q 'result=passed'
assert_fails_with 'spec464-topology-handoff-type' scripts/validate-agent-handoff --file "${handoff_dir}/validation-forbidden.json" --format text
)
comparability_repo="$(new_gate_repo)"
(
cd "${comparability_repo}"
cases="${fixture_root}/scripts/tests/fixtures/agent-quality-gates/baseline-comparability/cases.json"
projections="$(mktemp -d "${TMPDIR:-/tmp}/baseline-comparability.XXXXXX")"
php -r '
$cases = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR)["cases"];
$target = $argv[2];
$hash = static fn (string $character): string => str_repeat($character, 64);
$passingTest = static fn (string $identity, bool $introduced = true): array => [
"identity_sha256" => $identity,
"source_path" => "apps/platform/tests/Feature/Guards/NewContractTest.php",
"status" => "PASS",
"assertion_count" => 1,
"material_cause_sha256" => null,
"introduced_by_candidate_diff" => $introduced,
];
foreach ($cases as $caseId => $expectation) {
$fields = [
"execution_profile_identity" => $hash("a"),
"process_count" => $hash("b"),
"db_mode" => $hash("c"),
"fixture_mode" => $hash("d"),
"config_source" => $hash("e"),
"config_cache_status" => $hash("f"),
"environment_presence_signals" => $hash("1"),
];
$failure = $hash("2");
$cause = $hash("3");
$comparison = [
"attribution_claimed" => true,
"environment" => [
"comparison_profile_id" => "test-v1",
"candidate_field_sha256" => $fields,
"baseline_field_sha256" => $fields,
],
"actual_command" => [
"command_authority" => "REGISTRY",
"candidate_registry_definition_identity" => $hash("4"),
"baseline_registry_definition_identity" => $hash("4"),
"candidate_actual_invocation_identity" => $hash("5"),
"baseline_actual_invocation_identity" => $hash("5"),
"manual_override_used" => false,
"fallback_used" => false,
],
"test_set" => [
"candidate_selection_identity" => $hash("6"),
"baseline_selection_identity" => $hash("6"),
"candidate_test_set_identity" => $hash("7"),
"baseline_test_set_identity" => $hash("7"),
"shared_test_count" => 2,
"shared_test_set_sha256" => $hash("8"),
"candidate_only_tests" => [],
"baseline_only_tests" => [],
"candidate_failure_identities" => [$failure],
"baseline_failure_identities" => [$failure],
"shared_failure_causes" => [[
"failure_identity_sha256" => $failure,
"candidate_material_cause_sha256" => $cause,
"baseline_material_cause_sha256" => $cause,
]],
"candidate_only_failure_identities" => [],
"candidate_only_error_identities" => [],
"candidate_skipped_count" => 0,
"baseline_skipped_count" => 0,
"projection_ambiguous" => false,
"manual_relationship_requested" => false,
],
];
$posture = [
"allowed_test_set_relationships" => ["EXACT", "MONOTONE_SUPERSET"],
"authorization_signal_ids" => ["quality-gate-authority"],
"registry_definition_change_authorized" => true,
];
$makeMonotone = static function () use (&$comparison, $passingTest, $hash): void {
$comparison["test_set"]["candidate_only_tests"] = [$passingTest($hash("9"))];
$comparison["test_set"]["candidate_selection_identity"] = $hash("0");
$comparison["test_set"]["candidate_test_set_identity"] = $hash("a");
};
switch ($caseId) {
case "monotone_superset":
$makeMonotone();
$comparison["actual_command"]["candidate_registry_definition_identity"] = $hash("b");
break;
case "shared_baseline_improvement":
$comparison["test_set"]["candidate_failure_identities"] = [];
$comparison["test_set"]["shared_failure_causes"] = [];
break;
case "normal_feature_monotone_attempt":
$makeMonotone();
$posture = ["allowed_test_set_relationships" => ["EXACT"], "authorization_signal_ids" => [], "registry_definition_change_authorized" => false];
break;
case "candidate_only_failure":
$makeMonotone();
$comparison["test_set"]["candidate_only_tests"][0]["status"] = "FAIL";
$comparison["test_set"]["candidate_only_tests"][0]["material_cause_sha256"] = $hash("c");
$comparison["test_set"]["candidate_only_failure_identities"] = [$hash("d")];
$comparison["test_set"]["candidate_failure_identities"][] = $hash("d");
break;
case "candidate_only_skipped":
$makeMonotone();
$comparison["test_set"]["candidate_only_tests"][0]["status"] = "SKIPPED";
break;
case "candidate_only_not_introduced":
$makeMonotone();
$comparison["test_set"]["candidate_only_tests"][0]["introduced_by_candidate_diff"] = false;
break;
case "baseline_test_removed":
$comparison["test_set"]["baseline_only_tests"] = [$passingTest($hash("e"), false)];
$comparison["test_set"]["candidate_selection_identity"] = $hash("f");
$comparison["test_set"]["candidate_test_set_identity"] = $hash("1");
break;
case "shared_failure_added":
$comparison["test_set"]["candidate_failure_identities"][] = $hash("e");
break;
case "shared_failure_cause_changed":
$comparison["test_set"]["shared_failure_causes"][0]["candidate_material_cause_sha256"] = $hash("f");
break;
case "actual_invocation_mismatch":
$comparison["actual_command"]["baseline_actual_invocation_identity"] = $hash("f");
break;
case "fixture_identity_mismatch":
$comparison["environment"]["baseline_field_sha256"]["fixture_mode"] = $hash("f");
break;
case "db_mode_mismatch":
$comparison["environment"]["baseline_field_sha256"]["db_mode"] = $hash("f");
break;
case "execution_profile_mismatch":
$comparison["environment"]["baseline_field_sha256"]["execution_profile_identity"] = $hash("f");
break;
case "test_set_projection_ambiguous":
$comparison["test_set"]["projection_ambiguous"] = true;
break;
case "manual_monotone_activation":
$makeMonotone();
$comparison["test_set"]["manual_relationship_requested"] = true;
break;
}
file_put_contents($target."/".$caseId.".json", json_encode([
"comparison" => $comparison,
"posture" => $posture,
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
}
' "${cases}" "${projections}"
while IFS=$'\t' read -r case_id expected_state expected_reason; do
set +e
output="$(scripts/run-agent-quality-gates baseline-comparison \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--gate fast-feedback --base HEAD \
--projection-fixture "${projections}/${case_id}.json" --format json 2>&1)"
status=$?
set -e
if [[ "${expected_state}" == "COMPARABLE" ]]; then
[[ ${status} -eq 0 ]]
jq -e '.comparison.state == "COMPARABLE" and .candidate_disposition == "PASS_WITH_ATTRIBUTED_BASELINE"' <<<"${output}" >/dev/null
else
[[ ${status} -ne 0 ]]
jq -e --arg reason "${expected_reason}" '.comparison.state == "NOT_COMPARABLE" and (.comparison.reason_codes | index($reason)) != null' <<<"${output}" >/dev/null
fi
done < <(jq -r '.cases | to_entries[] | [.key, .value.expected_state, (.value.expected_reason_code // "")] | @tsv' "${cases}")
php -r '
define("TENANTPILOT_AGENT_QUALITY_GATES_LIBRARY_ONLY", true);
require ".agent/quality-gates/lib/agent-quality-gates.php";
$fixture = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR)["cause_normalization_cases"];
$qualityGates = new AgentQualityGates();
$comparison = new ReflectionMethod(AgentQualityGates::class, "materialCauseComparisonFromDiagnostics");
$counts = ["positive" => 0, "negative" => 0];
foreach (["positive" => "NORMALIZED_MATCH", "negative" => "MATERIAL_MISMATCH"] as $group => $expected) {
foreach ($fixture[$group] as $caseId => $case) {
$candidateStatus = $case["candidate_status"] ?? "FAIL";
$baselineStatus = $case["baseline_status"] ?? "FAIL";
$candidateType = $case["candidate_failure_type"] ?? $case["failure_type"] ?? "PHPUnit\\Framework\\ExpectationFailedException";
$baselineType = $case["baseline_failure_type"] ?? $case["failure_type"] ?? "PHPUnit\\Framework\\ExpectationFailedException";
$candidateLine = $case["candidate_source_line"] ?? $case["source_line"] ?? null;
$baselineLine = $case["baseline_source_line"] ?? $case["source_line"] ?? null;
$first = $comparison->invoke(
$qualityGates,
$case["candidate"],
$case["baseline"],
$candidateStatus,
$baselineStatus,
$candidateType,
$baselineType,
$candidateLine,
$baselineLine,
);
$second = $comparison->invoke(
$qualityGates,
$case["candidate"],
$case["baseline"],
$candidateStatus,
$baselineStatus,
$candidateType,
$baselineType,
$candidateLine,
$baselineLine,
);
if (($first["classification"] ?? null) !== $expected || $first !== $second) {
fwrite(STDERR, "cause-normalization-case-failed:{$group}:{$caseId}\n");
exit(1);
}
$counts[$group]++;
}
}
if ($counts !== ["positive" => 11, "negative" => 31]) {
fwrite(STDERR, "cause-normalization-case-count-mismatch\n");
exit(1);
}
' "${cases}"
)
echo "result=passed suite=run-agent-quality-gates"
printf 'result=passed suite=run-agent-quality-gates tests=5\n'

View File

@ -1,98 +1,221 @@
#!/usr/bin/env bash
set -euo pipefail
source "$(git rev-parse --show-toplevel)/scripts/tests/agent-quality-gates-test-lib"
spec="specs/459-mechanical-agent-quality-gates-validation-routing"
scripts/run-agent-quality-gates audit --spec "${spec}" --format text >/tmp/validate-spec-package.out
rg -q 'result=passed gate=spec-package' /tmp/validate-spec-package.out
scripts/validate-spec-package audit --spec specs/460-post-integration-additive-correction-contract-v1 --format text | rg -q 'result=passed gate=spec-package'
repo="$(new_gate_repo)"
(
cd "${repo}"
scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text | rg -q 'result=passed'
scripts/validate-spec-package implementation --spec specs/459-mechanical-agent-quality-gates-validation-routing --format json | rg -q '"result": "passed"'
git branch -M wrong-fixture-branch
assert_fails_with 'branch-mismatch' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
git branch -M 459-mechanical-agent-quality-gates-validation-routing
rm specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
assert_fails_with 'missing-json' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
printf '{invalid-json\n' > specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
assert_fails_with 'invalid-json' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
write_contract "${repo}" "../bad/"
assert_fails_with 'schema:.*pattern\\|invalid-path' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
write_contract "${repo}" "allowed/" "Completed"
assert_fails_with 'status-not-allowed' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
write_contract "${repo}" "allowed/" "Ready for implementation"
printf 'TODO unresolved\n' >> specs/459-mechanical-agent-quality-gates-validation-routing/spec.md
assert_fails_with 'unresolved-placeholder' scripts/validate-spec-package readiness --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
)
repo="$(new_gate_repo)"
(
cd "${repo}"
write_contract "${repo}" "allowed/" "Implemented"
git add specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json
git commit -q -m implemented-target
scripts/validate-spec-package audit --spec specs/459-mechanical-agent-quality-gates-validation-routing --format text | rg -q 'result=passed'
assert_fails_with 'correction-contract-not-allowed-in-audit' scripts/validate-spec-package audit --spec specs/459-mechanical-agent-quality-gates-validation-routing --correction-contract /tmp/nope.json --format text
)
repo="$(new_gate_repo)"
(
cd "${repo}"
cp -R specs/459-mechanical-agent-quality-gates-validation-routing specs/458-explicit-target-fixture
php -r '
$routingPath = ".agent/quality-gates/change-impact-rules.json";
$routing = json_decode(file_get_contents($routingPath), true, 512, JSON_THROW_ON_ERROR);
array_unshift($routing["rules"], [
"prefix" => "allowed/",
"classification" => "fixture-allowed",
"gates" => ["diff-scope", "diff-safety", "unicode", "change-validation"],
"skills" => ["spec-readiness-gate"],
"risks" => ["fixture-truth"],
]);
file_put_contents($routingPath, json_encode($routing, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
foreach ([
"specs/459-mechanical-agent-quality-gates-validation-routing" => [459, "mechanical-agent-quality-gates-validation-routing", "459-mechanical-agent-quality-gates-validation-routing"],
"specs/458-explicit-target-fixture" => [458, "explicit-target-fixture", "458-explicit-target-fixture"],
] as $spec => [$id, $slug, $branch]) {
$path = $spec."/execution-contract.json";
$contract = json_decode(file_get_contents($path), true, 512, JSON_THROW_ON_ERROR);
$contract["spec"]["id"] = $id;
$contract["spec"]["slug"] = $slug;
$contract["spec"]["path"] = $spec;
$contract["spec"]["branch"] = $branch;
$contract["spec"]["status"] = "Implemented";
file_put_contents($path, json_encode($contract, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
}
'
git add specs .agent/quality-gates/change-impact-rules.json
git commit -q -m explicit-historical-targets
start_head="$(git rev-parse HEAD)"
git checkout -q -b correction-package-session
output="$(TENANTPILOT_AGENT_GATE_FIXTURE_MODE=1 TENANTPILOT_AGENT_GATE_FIXTURE_TIME=2026-08-01T00:00:00Z scripts/issue-additive-correction-contract \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--session-branch correction-package-session \
--start-head "${start_head}" \
--integration-base HEAD \
--integration-target HEAD \
--diff-baseline HEAD \
--summary 'Package propagation.' \
--root-cause 'Historical active phases previously accepted partial identity.' \
--path allowed/fix.txt \
--format text)"
correction_contract="$(awk -F= '$1 == "contract" { print $2; exit }' <<<"${output}")"
mkdir -p allowed
printf 'candidate\n' > allowed/fix.txt
repository_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)"
suite_root="$(mktemp -d "${TMPDIR:-/tmp}/lean-spec-package.XXXXXX")"
passed=0
scripts/validate-spec-package implementation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--correction-contract "${correction_contract}" \
--format text | rg -q 'result=passed'
assert_fails_with 'branch-mismatch' scripts/validate-spec-package implementation \
--spec specs/459-mechanical-agent-quality-gates-validation-routing --format text
assert_fails_with 'correction-target-spec-mismatch' scripts/validate-spec-package implementation \
--spec specs/458-explicit-target-fixture --correction-contract "${correction_contract}" --format text
assert_fails_with 'correction-contract-not-allowed-in-audit' scripts/validate-spec-package audit \
--spec specs/459-mechanical-agent-quality-gates-validation-routing \
--correction-contract "${correction_contract}" --format text
cleanup() {
[[ -d "${suite_root}" ]] && rm -rf -- "${suite_root}"
}
trap cleanup EXIT INT TERM
pass() {
passed=$((passed + 1))
printf 'PASS %s\n' "$1"
}
new_fixture() {
local name="$1"
local risk_class="$2"
local repo="${suite_root}/${name}"
local spec="${repo}/specs/471-lean-package-fixture"
git -C "${suite_root}" init -q "${name}"
git -C "${repo}" config user.name 'Lean Spec Fixture'
git -C "${repo}" config user.email 'lean-spec@example.invalid'
mkdir -p "${repo}/.agent" "${repo}/scripts" "${spec}/checklists"
cp -R "${repository_root}/.agent/quality-gates" "${repo}/.agent/quality-gates"
cp "${repository_root}/scripts/validate-spec-package" "${repo}/scripts/validate-spec-package"
chmod +x "${repo}/scripts/validate-spec-package"
cat >"${spec}/spec.md" <<EOF
# Feature Specification: Lean Package Fixture
## Risk Classification
- **Risk Class**: ${risk_class}
## Scope
One bounded repository-governance fixture.
## Acceptance Criteria
1. The active Lean package validates deterministically.
## Test Strategy
Run only the focused package validator.
EOF
cat >"${spec}/plan.md" <<'EOF'
# Implementation Plan: Lean Package Fixture
## Test Strategy
Run only the focused package validator.
## Final Review
Exactly one final reviewer checks the frozen Candidate.
EOF
cat >"${spec}/tasks.md" <<'EOF'
# Tasks: Lean Package Fixture
- [ ] T001 Validate scope.
- [ ] T002 Run focused tests.
- [ ] T003 Obtain final reviewer approval.
EOF
cat >"${spec}/checklists/requirements.md" <<'EOF'
# Requirements
- [x] Scope, test strategy, and final review are explicit.
EOF
git -C "${repo}" add .
git -C "${repo}" commit -q -m fixture
printf '%s\n' "${repo}"
}
write_critical_contract() {
local repo="$1"
local separate_authority="${2:-true}"
local spec="${repo}/specs/471-lean-package-fixture"
cat >"${spec}/execution-contract.json" <<EOF
{
"schema_version": 1,
"risk_class": "CRITICAL",
"scope": {
"allowed_paths": ["AGENTS.md"],
"forbidden_paths": ["apps/platform/app/"]
},
"required_gates": ["foundation-regression"],
"remote_boundary": {
"provider": "GITEA",
"remote": "origin",
"target": "platform-dev",
"mutations_require_separate_authority": ${separate_authority},
"github_mutation_allowed": false
}
}
EOF
}
assert_passes() {
local repo="$1"
local risk_class="$2"
local output
output="$(git -C "${repo}" rev-parse --show-toplevel >/dev/null && cd "${repo}" && \
scripts/validate-spec-package readiness \
--spec specs/471-lean-package-fixture \
--risk-class "${risk_class}" \
--format text)"
rg -q '^result=passed gate=spec-package violations=0$' <<<"${output}"
}
assert_fails_with() {
local expected="$1"
shift
local output status
set +e
output="$("$@" 2>&1)"
status=$?
set -e
[[ ${status} -ne 0 ]]
rg -q -- "${expected}" <<<"${output}"
}
repo="$(new_fixture fast FAST)"
assert_passes "${repo}" FAST
pass 'FAST package without execution contract'
printf '{}\n' >"${repo}/specs/471-lean-package-fixture/execution-contract.json"
(
cd "${repo}"
assert_fails_with 'lean-execution-contract-not-allowed:FAST' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture \
--risk-class FAST --format text
)
echo "result=passed suite=validate-spec-package"
pass 'FAST execution contract requirement refused'
repo="$(new_fixture fast-missing-scope FAST)"
sed -i.bak '/^## Scope$/d' "${repo}/specs/471-lean-package-fixture/spec.md"
rm -f -- "${repo}/specs/471-lean-package-fixture/spec.md.bak"
(
cd "${repo}"
assert_fails_with 'missing-section:spec.md:scope' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture \
--risk-class FAST --format text
)
pass 'FAST missing scope refused'
repo="$(new_fixture normal NORMAL)"
assert_passes "${repo}" NORMAL
pass 'NORMAL package without execution contract'
sed -i.bak '/^## Test Strategy$/d' "${repo}/specs/471-lean-package-fixture/plan.md"
rm -f -- "${repo}/specs/471-lean-package-fixture/plan.md.bak"
(
cd "${repo}"
assert_fails_with 'missing-section:plan.md:test-strategy' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture \
--risk-class NORMAL --format text
)
pass 'NORMAL missing test strategy refused'
repo="$(new_fixture critical CRITICAL)"
write_critical_contract "${repo}"
assert_passes "${repo}" CRITICAL
pass 'CRITICAL compact contract'
write_critical_contract "${repo}" false
(
cd "${repo}"
assert_fails_with 'critical-contract-remote-boundary' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture \
--risk-class CRITICAL --format text
)
pass 'CRITICAL missing remote authority boundary refused'
repo="$(new_fixture unknown EXPERIMENTAL)"
(
cd "${repo}"
assert_fails_with 'unknown-risk-class:EXPERIMENTAL' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture --format text
)
pass 'unknown risk class refused'
repo="$(new_fixture contradictory FAST)"
printf '\n- **Risk Class**: NORMAL\n' >>"${repo}/specs/471-lean-package-fixture/spec.md"
(
cd "${repo}"
assert_fails_with 'contradictory-risk-classification' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture --format text
)
pass 'contradictory risk classification refused'
repo="$(new_fixture no-fallback FAST)"
sed -i.bak '/^- \*\*Risk Class\*\*:/d' "${repo}/specs/471-lean-package-fixture/spec.md"
rm -f -- "${repo}/specs/471-lean-package-fixture/spec.md.bak"
cat >"${repo}/specs/471-lean-package-fixture/execution-contract.json" <<'EOF'
{"schema_version":1,"spec":{"id":459},"scope":{"allow":[],"deny":[]}}
EOF
(
cd "${repo}"
assert_fails_with 'risk-class-missing-in-spec' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture --format text
)
pass 'legacy-shaped package has no fallback'
repo="$(new_fixture correction-retired FAST)"
(
cd "${repo}"
assert_fails_with 'lean-correction-contract-not-allowed' \
scripts/validate-spec-package readiness --spec specs/471-lean-package-fixture \
--risk-class FAST --correction-contract /tmp/retired.json --format text
)
pass 'correction contract entry refused'
printf 'result=passed suite=validate-spec-package tests=%d legacy_fallback=0\n' "${passed}"

View File

@ -129,9 +129,6 @@ candidate_path_allowed() {
scripts/tests/codex-agent-reference-workflow)
return 0
;;
specs/458-codex-agent-foundation-safe-local-finalization/*)
return 0
;;
esac
active_contract_path_allowed "${path}"
@ -145,6 +142,22 @@ active_contract_path_allowed() {
php -r '
$contract = json_decode(file_get_contents($argv[1]), true, 512, JSON_THROW_ON_ERROR);
$path = $argv[2];
foreach (($contract["scope"]["forbidden_paths"] ?? []) as $forbiddenPath) {
if (!is_string($forbiddenPath) || $forbiddenPath === "") {
continue;
}
if ($forbiddenPath === $path || (str_ends_with($forbiddenPath, "/") && str_starts_with($path, $forbiddenPath))) {
exit(1);
}
}
foreach (($contract["scope"]["allowed_paths"] ?? []) as $allowedPath) {
if (!is_string($allowedPath) || $allowedPath === "") {
continue;
}
if ($allowedPath === $path || (str_ends_with($allowedPath, "/") && str_starts_with($path, $allowedPath))) {
exit(0);
}
}
foreach (($contract["scope"]["deny"] ?? []) as $entry) {
$entryPath = (string) ($entry["path"] ?? "");
if (($entry["kind"] ?? "") === "file" && $entryPath === $path) {
@ -225,8 +238,6 @@ cd "${repository_root}"
current_branch="$(git branch --show-current)"
if [[ -n "${current_branch}" && -f "specs/${current_branch}/execution-contract.json" ]]; then
active_quality_gate_contract="specs/${current_branch}/execution-contract.json"
elif [[ -f "specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json" ]]; then
active_quality_gate_contract="specs/459-mechanical-agent-quality-gates-validation-routing/execution-contract.json"
fi
resolver_output="$(
@ -234,7 +245,6 @@ resolver_output="$(
--branch-family="${branch_family}" \
--work-classification="${work_classification}" \
--pull-request-target="${pull_request_target}" \
--spec=specs/458-codex-agent-foundation-safe-local-finalization/spec.md \
--format=kv
)" || {
record_failure "canonical feature baseline resolves"
@ -396,11 +406,11 @@ instruction_preservation_ok() {
"Multi-Agent Coordination" \
"Codex Agent Foundation" \
"Mechanical Agent Quality Gates" \
"scripts/run-agent-quality-gates" \
"scripts/issue-agent-finalization-receipt" \
"scripts/issue-additive-correction-contract" \
"separate current user correction authority" \
"separate current user commit authority"
"The Main Agent is the sole writer" \
"Risk determines test depth" \
"FAST and NORMAL do not create or consume" \
"scripts/codex-safe-local-commit" \
"separate explicit current authority"
}
check "canonical instructions preserve governance and add the foundation" instruction_preservation_ok
@ -419,21 +429,17 @@ active_references_ok() {
check "active references use canonical AGENTS.md" active_references_ok
roles_ok() {
local expected_files actual_files file expected_name expected_model expected_reasoning
local file expected_name expected_model expected_reasoning
local expected_sandbox expected_approval
local names_file
expected_files=$'code-explorer.toml\ncode-reviewer.toml\ngit-finalizer.toml\nimplementer.toml\ntest-validator.toml'
actual_files="$(
find .codex/agents -maxdepth 1 -type f -name '*.toml' -exec basename {} \; 2>/dev/null |
LC_ALL=C sort
)"
[[ "${actual_files}" == "${expected_files}" ]] || return 1
names_file="$(mktemp "${TMPDIR:-/tmp}/codex-agent-names.XXXXXX")"
validator_temporaries+=("${names_file}")
for file in .codex/agents/*.toml; do
for file in \
.codex/agents/code-explorer.toml \
.codex/agents/test-validator.toml \
.codex/agents/code-reviewer.toml; do
file_contains_all "${file}" \
'name = "' \
'description = "' \
@ -451,13 +457,6 @@ roles_ok() {
expected_sandbox="read-only"
expected_approval="never"
;;
implementer.toml)
expected_name="implementer"
expected_model="gpt-5.5"
expected_reasoning="high"
expected_sandbox="workspace-write"
expected_approval="on-request"
;;
test-validator.toml)
expected_name="test_validator"
expected_model="gpt-5.4-mini"
@ -472,13 +471,6 @@ roles_ok() {
expected_sandbox="read-only"
expected_approval="never"
;;
git-finalizer.toml)
expected_name="git_finalizer"
expected_model="gpt-5.4-mini"
expected_reasoning="medium"
expected_sandbox="workspace-write"
expected_approval="on-request"
;;
*)
return 1
;;
@ -492,15 +484,14 @@ roles_ok() {
printf '%s\n' "${expected_name}" >>"${names_file}"
done
[[ "$(LC_ALL=C sort -u "${names_file}" | wc -l | tr -d ' ')" == "5" ]]
file_contains_all .codex/agents/code-explorer.toml 'EXPLORATION_HANDOFF' || return 1
file_contains_all .codex/agents/implementer.toml 'IMPLEMENTATION_HANDOFF' 'scripts/run-agent-quality-gates preflight' '--correction-contract' || return 1
file_contains_all .codex/agents/test-validator.toml 'VALIDATION_HANDOFF' 'resolved change-validation plan' 'additive correction contract' || return 1
file_contains_all .codex/agents/code-reviewer.toml 'REVIEW_RESULT' 'implementation report evidence block' 'additive history preservation' || return 1
file_contains_all .codex/agents/git-finalizer.toml 'Spec-459 finalization receipt' '--receipt' '--correction-contract' || return 1
[[ "$(LC_ALL=C sort -u "${names_file}" | wc -l | tr -d ' ')" == "3" ]]
file_contains_all AGENTS.md \
'`code_explorer` is optional, read-only, and off by default' \
'may require at most one' \
'exactly one independent final `code_reviewer`' || return 1
}
check "exact five role files match the approved matrix" roles_ok
check "Lean active roles are bounded and retired role files are not registered" roles_ok
configuration_ok() {
file_contains_all .codex/config.toml \
@ -508,24 +499,19 @@ configuration_ok() {
'max_threads = 3' \
'[agents.code_explorer]' \
'config_file = "agents/code-explorer.toml"' \
'[agents.implementer]' \
'config_file = "agents/implementer.toml"' \
'[agents.test_validator]' \
'config_file = "agents/test-validator.toml"' \
'[agents.code_reviewer]' \
'config_file = "agents/code-reviewer.toml"' \
'[agents.git_finalizer]' \
'config_file = "agents/git-finalizer.toml"' \
'[sandbox_workspace_write]' \
'network_access = false' \
'[mcp_servers.laravel-boost]' \
'[mcp_servers.laravel-boost.tools.search-docs]' \
'[mcp_servers.laravel-boost.tools.tinker]' \
'[mcp_servers.gitea]' &&
[[ "$(rg -c '^\[agents\.[a-z_]+\]$' .codex/config.toml)" == "5" ]]
'[mcp_servers.gitea]'
}
check "project config preserves MCP and applies bounded agent defaults" configuration_ok
check "project config preserves MCP and exposes the bounded Lean tool roles" configuration_ok
model_catalog_ok() {
codex debug models --bundled |
@ -571,6 +557,8 @@ helper_contract_ok() {
[[ -x scripts/codex-safe-local-commit ]] || return 1
! rg -n 'git[[:space:]].*(push|fetch|pull)|--no-verify|reset[[:space:]]+--hard|git[[:space:]]+clean' \
scripts/codex-safe-local-commit || return 1
! rg -n '^[[:space:]]+(validate_finalization_receipt|validate_correction_contract|consume_finalization_receipt)$' \
scripts/codex-safe-local-commit || return 1
file_contains_all scripts/codex-safe-local-commit \
'normalize_and_validate_paths' \
'assert_rename_authority' \
@ -583,8 +571,9 @@ helper_contract_ok() {
'index-lock-unavailable' \
'stale-fingerprint' \
'candidate-drift' \
'correction-contract' \
'receipt' \
'legacy-mode-retired' \
'lean-correction-contract-not-allowed' \
'lean-receipt-not-allowed' \
'helper_remote_commands=0' \
'.env.*' \
'*.pem' \
@ -600,22 +589,22 @@ tests_present_ok() {
[[ -x scripts/tests/codex-agent-reference-workflow ]] || return 1
[[ -f apps/platform/tests/Feature/Guards/CodexAgentFoundationContractTest.php ]] || return 1
file_contains_all scripts/tests/codex-safe-local-commit-test \
'case_tracked_success' \
'case_mixed_paths_and_encoding' \
'case_rename_requires_both_paths' \
'--lean-control-plane' \
'case_lean_commit_without_receipt' \
'case_lean_commit_authority_and_scope_refusals' \
'case_stale_fingerprint_refuses_without_mutation' \
'case_hook_rejection_preserves_real_state' \
'case_hook_staged_drift_refuses_publication' \
'case_signal_cleanup' || return 1
'legacy-mode-retired' || return 1
file_contains_all scripts/tests/codex-agent-reference-workflow \
'code_explorer' \
'implementer' \
'test_validator' \
'code_reviewer' \
'git_finalizer' \
'validation-failed' \
'review-failed' \
'cycle-limit-escalated'
'main_agent=1' \
'explorer=optional' \
'implementer=retired' \
'test_validator_max=1' \
'code_reviewer=1' \
'parallel-writers-refused' \
'critical-validator-limit-refused' \
'remote-authority-refused' \
'active-control-plane-reference-check'
}
check "required deterministic test and reference proof is present" tests_present_ok
@ -695,7 +684,7 @@ candidate_identity_ok() {
manifest="$(mktemp "${TMPDIR:-/tmp}/codex-agent-review-manifest.XXXXXX")"
validator_temporaries+=("${manifest}")
{
printf 'tenantpilot-spec-458-review-candidate-v2\0'
printf 'tenantpilot-agent-foundation-review-candidate-v3\0'
printf 'branch\0%s\0' "${candidate_identity_branch}"
printf 'head\0%s\0' "${candidate_identity_head}"
printf 'baseline\0%s\0' "${review_identity_baseline}"
@ -716,7 +705,7 @@ candidate_identity_ok() {
manifest="$(mktemp "${TMPDIR:-/tmp}/codex-agent-finalization-none.XXXXXX")"
validator_temporaries+=("${manifest}")
{
printf 'tenantpilot-spec-458-finalization-no-delta-v1\0'
printf 'tenantpilot-agent-foundation-finalization-no-delta-v2\0'
printf 'head\0%s\0' "${candidate_identity_head}"
printf 'tree\0%s\0' "${candidate_tree}"
} >"${manifest}"

View File

@ -0,0 +1,18 @@
# Requirements Checklist: Lean Agent Control-Plane Decoupling
- [x] Scope is limited to active Control-Plane entry points and focused tests.
- [x] Risk class is CRITICAL and Main Agent is the sole writer.
- [x] Exactly one validator and one distinct final reviewer are planned.
- [x] No product runtime, UI, provider, database, deployment, or workflow file is in scope.
- [x] Temporarily present obsolete files are unsupported dead code with zero active consumers and Spec-472/473 deletion ownership.
- [x] FAST/NORMAL packages do not require `execution-contract.json`.
- [x] CRITICAL work may use the compact contract included in Spec 471.
- [x] Unknown or contradictory risk remains fail-closed.
- [x] Git history is the historical archive; no current validator promises legacy-package execution.
- [x] Safe Commit retains expected HEAD, explicit paths, Candidate SHA, empty-index, unmerged-path, and review protection.
- [x] Fast Feedback/Profile/Calibration cleanup is assigned to Spec 473.
- [x] No compatibility mode, legacy dispatcher, fallback, adapter, or historical package loader is introduced.
- [x] Browser proof is `N/A - no rendered UI surface changed`.
- [x] Deployment impact is none.
- [x] Candidate scope is capped at 40 tracked paths.
- [x] GitHub mutations are forbidden.

View File

@ -0,0 +1,66 @@
{
"schema_version": 1,
"risk_class": "CRITICAL",
"scope": {
"allowed_paths": [
"AGENTS.md",
".agent/quality-gates/README.md",
".agent/quality-gates/change-impact-rules.json",
".agent/quality-gates/lib/agent-quality-gates.php",
".agent/quality-gates/validation-command-registry.json",
".agent/quality-gates/validation-routing-manifest.schema.json",
".agent/skills/workflows/spec-readiness-gate/SKILL.md",
".codex/config.toml",
".codex/agents/code-explorer.toml",
".codex/agents/test-validator.toml",
".codex/agents/code-reviewer.toml",
".codex/skills/",
".specify/templates/",
"docs/ai-coding-rules.md",
"docs/testing-guidelines.md",
"scripts/codex-safe-local-commit",
"scripts/tests/",
"scripts/validate-codex-agent-foundation",
"apps/platform/tests/Feature/Guards/AgentQualityGateRoutingContractTest.php",
"apps/platform/tests/Feature/Guards/CodexAgentFoundationContractTest.php",
"specs/471-lean-agent-control-plane-decoupling-v1/"
],
"forbidden_paths": [
"apps/platform/app/",
"apps/platform/database/",
"apps/platform/resources/",
".gitea/workflows/",
"specs/458-*/",
"specs/459-*/",
"specs/460-*/",
"specs/461-*/",
"specs/462-*/",
"specs/463-*/",
"specs/464-*/",
"specs/465-*/",
"specs/466-*/",
"specs/467-*/",
"specs/468-*/",
"specs/469-*/",
"specs/470-*/"
]
},
"required_gates": [
"spec-package",
"change-validation",
"quality-gate-self-tests",
"foundation-regression",
"diff-scope",
"diff-safety",
"unicode",
"git-diff-check",
"independent-review"
],
"remote_boundary": {
"provider": "GITEA",
"remote": "origin",
"target": "platform-dev",
"mutations_require_separate_authority": true,
"github_mutation_allowed": false
}
}

View File

@ -0,0 +1,96 @@
# Implementation Report: Lean Agent Control-Plane Decoupling
**Status**: Candidate validation complete; external freeze and review pending
## Scope
Active agent instructions, on-demand skills, Spec Kit templates, risk-aware
spec-package validation, routing/dispatch authority, Lean Safe Commit call
sites, and focused Control-Plane tests only. No product, UI, provider, database,
deployment, CI-workflow, or GitHub state is changed.
## Changed Paths
- 39 paths: 6 additions and 33 modifications; no deletion or rename.
- Active entry points: `AGENTS.md`, quality-gate policy/registry/dispatcher,
three registered read-only agent configs, active skills, Spec Kit templates,
testing/coding guidance, the Lean Safe Commit helper, Foundation validation,
focused shell/Pest contracts, and this six-file Spec 471 package.
- Active-plus-documentation diff, excluding Spec 471 and tests: 682 additions,
809 deletions, net -127 lines.
- Normative entry-point diff: 85 additions, 118 deletions, net -33 lines.
- Constitution amendment: no; the constitution did not mandate the retired
control-plane lifecycle.
## Validation
- PHP syntax: 3/3 changed PHP files PASS.
- JSON syntax: 4/4 changed JSON files PASS.
- Shell syntax: 7/7 changed executable shell files PASS.
- `scripts/tests/validate-spec-package-test`: 11/11 PASS; fallback count 0.
- `scripts/tests/resolve-change-validation-test`: 10/10 PASS; CRITICAL
Control-Plane authority selects focused governance proof without routing
Confidence or Heavy solely because the router or commit helper changed.
- `scripts/tests/codex-agent-reference-workflow`: 10/10 PASS.
- `scripts/tests/codex-safe-local-commit-test --lean-control-plane`: 5/5 PASS.
- `scripts/tests/run-agent-quality-gates-test`: 5/5 suites PASS.
- `scripts/validate-codex-agent-foundation`: 16/16 PASS; 39 paths.
- Focused Pest guards: 4 tests, 48 assertions PASS in isolated Sail. The two
lane-selected process guards also exit 0 in Sail after the
bounded portability correction.
- Diff scope, diff safety, Unicode, and `git diff --check`: PASS.
- Active Control-Plane reference proof: PASS; active legacy consumers and
fallback routes equal zero.
## Fast Feedback Diagnostic
- Active registry command: `./scripts/platform-test-lane fast-feedback`, with
no qualification or calibration flags.
- Functional payload executed once through the same canonical Composer/TestLane
selection in the isolated Spec-471 Sail project.
- Observed runner result before bounded remediation: 3,224 tests, 23,219
assertions, 2 failures, 0 errors, 0 skips, 742.306 seconds.
- Both failures were portable-tooling failures in the changed Control-Plane
process guards because the Sail image has no `rg`; 3,222 other tests passed.
- Bounded correction replaced that external test-only dependency with `grep`.
The two affected guards then passed in focused Sail/host proof; the full lane
was not repeated, as required by bounded remediation.
- Legacy report layer: `EXECUTION_PROFILE_UNQUALIFIED`, blocking, with no
calibration attempted. This is known Spec-473 physical-cleanup debt, not an
active Control-Plane requirement.
- Report: `apps/platform/storage/logs/test-lanes/fast-feedback-latest.report.json`
(ignored runtime artifact).
## Candidate and Independent Checks
- **FINAL_CANDIDATE_SHA256**: established externally after this report's bytes
are final; this field intentionally does not self-reference the fingerprint.
- **Validator**: pending
- **Final reviewer**: pending
## Product and Deployment Closeout
- Livewire v4 compliance: unchanged; no runtime UI code changed.
- Panel provider registration: unchanged; `apps/platform/bootstrap/providers.php`.
- Global search: no resource changed.
- Destructive/high-impact actions: none changed.
- Asset strategy: no assets; `filament:assets` not required.
- Browser: N/A - no rendered UI surface changed.
- Deployment: none.
- Closed specs: not rewritten by this changeset; Git history is the archive and
no current-tree retention promise is made.
## Open Risks
Known Profile/COST code remains explicit Spec-473 deletion debt and is not
changed by this Candidate. Dead finalization files remaining after this
changeset are unsupported and assigned to Spec 472. Neither category has an
active fallback, consumer, or support promise.
## Architectural Disposition
- `ACTIVE_CONTROL_PLANE = LEAN_ONLY`
- `LEGACY_CONTROL_PLANE = RETIRED`
- `GIT_HISTORY = HISTORICAL_ARCHIVE`
- `NO_COMPATIBILITY_PROMISE = TRUE`
- `PHYSICAL_DELETION_OF_DEAD_FILES = SPEC_472_OR_SPEC_473`

View File

@ -0,0 +1,91 @@
# Implementation Plan: Lean Agent Control-Plane Decoupling
**Branch**: `471-lean-agent-control-plane-decoupling-v1`
**Date**: 2026-08-11
**Spec**: `specs/471-lean-agent-control-plane-decoupling-v1/spec.md`
## Summary
Prune legacy lifecycle requirements from active instructions, on-demand skills,
Spec Kit templates, readiness validation, agent registration, Safe Commit
dispatch, and the reference workflow. Obsolete files that remain pending Spec
472/473 deletion are unsupported dead code; Git history is the only archive.
## Critical Execution Contract
`execution-contract.json` is present because this bounded changeset modifies the
central agent control plane. It contains only scope, required gates, and the
separate remote-authority boundary. FAST/NORMAL packages generated after this
change do not require that artifact.
## Technical Approach
1. Replace active five-role and lifecycle language with the Lean topology.
2. Keep product, tenant, RBAC, audit, provider, destructive-action, and remote
safety rules unchanged.
3. Make the existing spec-package validator Lean-only with no historical loader,
legacy dispatcher, or dual mode.
4. Replace the reference workflow and active Foundation proof with compact Lean
topology and helper checks; do not add tests for retired workflow support.
5. Run only focused Control-Plane validation and freeze one Candidate SHA-256.
## Changed Surfaces
- `AGENTS.md` and active governance/testing documentation.
- Control-plane skills under `.agent/skills/` and `.codex/skills/`.
- Spec Kit templates under `.specify/templates/`.
- `validateSpecPackage()` dispatch in the existing quality-gate engine.
- Focused shell and Pest foundation tests.
- Spec 471 documentation.
## Test Strategy
- `php -l .agent/quality-gates/lib/agent-quality-gates.php`
- `bash -n` for changed shell scripts.
- JSON validation for the compact CRITICAL contract.
- `scripts/tests/validate-spec-package-test`
- `scripts/tests/codex-agent-reference-workflow`
- Relevant Phase-1 routing and safe-commit selftests.
- Focused `CodexAgentFoundationContractTest` through Sail when available.
- Active-reference scan, diff scope/safety, Unicode, and `git diff --check`.
No Confidence, Heavy, calibration, profile qualification, baseline, sidecar, or
browser lane is run. Because this Candidate changes the active registry command
and routing selection, run exactly one functional Fast Feedback execution and
report its test result separately from known Spec-473 Profile/COST debt.
## Final Review
After focused validation, freeze one helper-compatible Candidate fingerprint.
Exactly one independent validator reviews that Candidate first. After validator
PASS, one distinct independent reviewer checks the unchanged Candidate and must
return `NO_CONFIRMED_FINDINGS`. Any mutation requires a new fingerprint.
## Safety and Retirement
- Main Agent is the only writer.
- Closed specs are not rewritten in this changeset; Git history is the
historical authority and no current-tree retention promise is created.
- Dead receipt/handoff/completion/correction files may temporarily remain, are
unsupported, have no active consumer, and are assigned to Spec 472 deletion.
- Dead profile/calibration/sidecar files may temporarily remain and are assigned
to Spec 473 deletion.
- Phase-1 Safe Commit protections are unchanged.
- Remote mutation is separate and limited to the user-authorized Gitea flow.
- GitHub remains passive with zero mutations.
## Constitution Check
PASS. The Constitution does not mandate the five-role, handoff, receipt,
completion, correction, calibration, profile, or universal execution-contract
control plane. No amendment is necessary. Product, tenant, RBAC, audit,
provider, secret, destructive-action, restore, deployment, and staging rules
remain unchanged.
## Complexity Check
No compatibility adapter, legacy runtime, engine, schema family, agent role,
evidence type, or lifecycle is added.
The active workflow loses steps and the reference workflow becomes materially
smaller. Documentation additions clarify the smaller topology; they do not add
operational stages.

View File

@ -0,0 +1,120 @@
# Feature Specification: Lean Agent Control-Plane Decoupling
**Feature Branch**: `471-lean-agent-control-plane-decoupling-v1`
**Created**: 2026-08-11
**Status**: In Progress
## Risk Classification
- **Risk Class**: CRITICAL
- **Why**: This changes repository-governance entry points and spec-package validation, but not product runtime, test-lane execution, or remote configuration.
- **Writer topology**: One Main Agent is the sole writer; one independent validator and one distinct final reviewer are required after Candidate freeze.
## Scope
Decouple the active agent control plane from the legacy five-role, handoff,
completion, receipt, correction-contract, calibration, and profile-qualification
lifecycle. Update only active instructions, on-demand skills, Spec Kit templates,
the risk-aware spec-package entry point, and focused reference/foundation tests.
`ACTIVE_CONTROL_PLANE = LEAN_ONLY`. Obsolete engines, schemas, scripts, fixtures,
spec packages, and role files may remain physically present only until their
assigned deletion in Spec 472 or 473. They are unsupported dead code, have no
active consumer or fallback contract, and Git history is the historical archive.
### In Scope
- Make Main Agent sole-writer topology authoritative in active instructions.
- Make FAST/NORMAL spec packages valid without `execution-contract.json`.
- Allow a compact execution contract for CRITICAL work.
- Keep risk routing fail-closed and final review mandatory.
- Remove active template and skill defaults that create role, handoff,
completion, receipt, correction, calibration, or profile tasks.
- Replace the five-role reference workflow with a Lean topology proof.
- Remove active dispatch to legacy package validation and legacy finalization.
### Out of Scope
- Bulk physical deletion of receipt, handoff, completion, correction, and old
spec-package files; Spec 472 owns that deletion.
- Changing Fast Feedback, TestLaneBudget, TestLaneManifest, TestLaneReport,
profile qualification, calibration, sidecars, or cause normalization.
- Changing product runtime, UI, providers, database, deployment, or CI workflows.
- Resuming Specs 463, 464, 465, 466, 467, or 468.
- Any GitHub mutation or Gitea policy change.
## Acceptance Criteria
1. FAST and NORMAL active entry points use the Main Agent as sole writer, no
default subagents, exactly one final reviewer, and no validator without a
CRITICAL signal.
2. FAST and NORMAL active entry points do not require handoffs, Completion
Evidence, receipts, receipt consumption, correction contracts, calibration,
profile qualification, or a Git-Finalizer agent.
3. The spec-package validator accepts complete FAST/NORMAL packages without an
execution contract and rejects missing scope, test strategy, unknown risk,
or contradictory risk declarations.
4. The spec-package validator accepts a compact CRITICAL execution contract with
required gates and a separate remote-authority boundary.
5. The active spec-package validator has one Lean path and no legacy dispatcher,
historical package loader, compatibility flag, or fallback route.
6. The reference workflow proves FAST, NORMAL, and CRITICAL Lean topologies,
bounded remediation, one Candidate fingerprint, one reviewer, Lean Safe
Commit inputs, and the separate remote boundary.
7. Dead files not needed by the active Control Plane are explicitly unsupported
and assigned to physical deletion in Spec 472 or 473.
8. Candidate scope remains at or below 40 tracked paths.
## Test Strategy
- Shell and PHP syntax for changed executable files.
- Focused spec-package positive and negative cases.
- Lean reference-workflow and Foundation contract tests.
- Existing Phase-1 risk/role-routing tests.
- Skill/template active-reference checks.
- Safe-Commit selftests only through unchanged Lean call sites.
- Diff scope, diff safety, Unicode, and `git diff --check`.
- Exactly one functional Fast Feedback run because the active registry command
and routing selection changed; test results and known Spec-473 Profile/COST
debt are reported separately.
## Product and Runtime Impact
- **UI Surface Impact**: N/A - no rendered UI surface changed.
- **Product Surface Contract**: N/A - repository control-plane only.
- **Livewire v4**: unchanged and compliant.
- **Panel providers**: no change; canonical location remains `apps/platform/bootstrap/providers.php`.
- **Global search**: no resource changed.
- **Destructive/high-impact product actions**: none changed.
- **Assets**: none; `filament:assets` is not required.
- **Deployment**: none; no env, migration, queue, scheduler, storage, container, staging, or production impact.
## Assumptions and Risks
- Phase-1 routing remains the mechanical risk authority and already resolves
FAST/NORMAL lifecycle gates to `NOT_REQUIRED`.
- Dead code may still contain retired vocabulary temporarily, but it has no
active consumer, support promise, or compatibility status and is assigned to
Spec 472/473 deletion.
- The known Fast Feedback Profile/COST behavior is Spec-473 debt and is not
changed here.
- A contradictory or unknown risk declaration must fail closed.
## Success Criteria
- **SC-001**: All focused Control-Plane tests pass with no active FAST/NORMAL
legacy-lifecycle requirement.
- **SC-002**: Active legacy consumers, fallback routes, and compatibility modes
equal zero; Git history remains unchanged as the historical archive.
- **SC-003**: The final candidate contains no more than 40 tracked paths and no
product/runtime or Spec-473 Fast Feedback implementation change.
## Architectural Disposition
- `ACTIVE_CONTROL_PLANE = LEAN_ONLY`
- `LEGACY_CONTROL_PLANE = RETIRED`
- `GIT_HISTORY = HISTORICAL_ARCHIVE`
- `NO_COMPATIBILITY_PROMISE = TRUE`
- `PHYSICAL_DELETION_OF_DEAD_FILES = SPEC_472_OR_SPEC_473`
- **SC-004**: Independent validation passes and the distinct final reviewer
reports `NO_CONFIRMED_FINDINGS` against the same Candidate SHA-256.

View File

@ -0,0 +1,42 @@
# Tasks: Lean Agent Control-Plane Decoupling
## Phase 1: Scope and Spec
- [x] T001 Record Phase-2A scope, non-goals, risk, and acceptance criteria in `specs/471-lean-agent-control-plane-decoupling-v1/spec.md`.
- [x] T002 Record the bounded implementation and validation approach in `specs/471-lean-agent-control-plane-decoupling-v1/plan.md`.
- [x] T003 Create the compact CRITICAL contract in `specs/471-lean-agent-control-plane-decoupling-v1/execution-contract.json`.
- [x] T004 Run exactly one compact consistency check across Spec 471 spec, plan, and tasks.
## Phase 2: Active Control Plane
- [x] T005 Replace legacy default topology and lifecycle requirements in `AGENTS.md` and active governance/testing documentation.
- [x] T006 Update the readiness and Gitea/finish skills to use Lean local closeout and a separate remote boundary.
- [x] T007 Update the Spec Kit plan/tasks/implement/loop skills to generate one-writer Lean work.
- [x] T008 Update active Spec Kit templates so FAST/NORMAL do not require execution contracts or legacy lifecycle tasks.
- [x] T009 Make package validation Lean-only and remove legacy dispatch from `.agent/quality-gates/lib/agent-quality-gates.php`.
## Phase 3: Focused Contract Tests
- [x] T010 Add FAST, NORMAL, CRITICAL, contradictory, unknown-risk, and no-fallback cases to `scripts/tests/validate-spec-package-test`.
- [x] T011 Replace the five-role reference fixture with Lean topology and helper proofs in `scripts/tests/codex-agent-reference-workflow`.
- [x] T012 Update the focused Foundation contract test for the Lean reference workflow.
- [x] T013 Prove active skills, templates, agent registration, package dispatch, and Safe Commit contain no legacy fallback.
## Phase 4: Validation and Freeze
- [x] T014 Run focused syntax, package, reference, routing, foundation, safe-commit, scope, safety, Unicode, and diff checks.
- [x] T015 Run one functional Fast Feedback selection, remediate its two portable-tooling failures within scope, and separate the functional proof from known Spec-473 Profile/COST debt.
- [ ] T016 Freeze one `FINAL_CANDIDATE_SHA256` over the exact Candidate paths.
- [ ] T017 Obtain exactly one independent validator PASS against the frozen Candidate.
- [ ] T018 Obtain exactly one distinct final reviewer `NO_CONFIRMED_FINDINGS` verdict against the same Candidate.
## Phase 5: Authorized Delivery
- [ ] T019 Commit exactly once through `scripts/codex-safe-local-commit` Lean mode.
- [ ] T020 Re-fetch only `origin`, prove `platform-dev` did not move, and push the authorized Phase-2A branch.
- [ ] T021 Create the authorized Gitea PR and observe only existing required checks.
- [ ] T022 Squash-merge only under the stated check/admin conditions and prove the post-merge tree content.
T016-T022 are post-document-freeze closeout actions. Their authoritative state
is reported outside Candidate bytes so completing them cannot invalidate the
Candidate hash reviewed by the validator and reviewer.