Lean Governance Phase 2A: Decouple Active Control Plane #533
@ -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.
|
||||
|
||||
@ -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"]
|
||||
},
|
||||
{
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
107
AGENTS.md
@ -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)
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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',
|
||||
);
|
||||
});
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
@ -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 ]]
|
||||
|
||||
@ -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 ]]
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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.
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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`
|
||||
91
specs/471-lean-agent-control-plane-decoupling-v1/plan.md
Normal file
91
specs/471-lean-agent-control-plane-decoupling-v1/plan.md
Normal 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.
|
||||
120
specs/471-lean-agent-control-plane-decoupling-v1/spec.md
Normal file
120
specs/471-lean-agent-control-plane-decoupling-v1/spec.md
Normal 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.
|
||||
42
specs/471-lean-agent-control-plane-decoupling-v1/tasks.md
Normal file
42
specs/471-lean-agent-control-plane-decoupling-v1/tasks.md
Normal 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.
|
||||
Reference in New Issue
Block a user