From abdec60d7a94a661e89da9e301c775433042e716 Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Fri, 17 Apr 2026 19:59:25 +0200 Subject: [PATCH] Spec 210: implement CI test matrix budget enforcement --- .gitea/workflows/test-browser.yml | 74 ++++ .gitea/workflows/test-heavy-governance.yml | 74 ++++ .gitea/workflows/test-main-confidence.yml | 62 +++ .gitea/workflows/test-pr-fast-feedback.yml | 64 +++ .github/agents/copilot-instructions.md | 4 +- .specify/README.md | 1 + .specify/memory/constitution.md | 55 +-- .specify/templates/checklist-template.md | 1 + .specify/templates/plan-template.md | 14 + .specify/templates/spec-template.md | 19 + .specify/templates/tasks-template.md | 7 + README.md | 20 + .../Guards/BrowserLaneIsolationTest.php | 13 +- .../CiConfidenceWorkflowContractTest.php | 21 + .../CiFastFeedbackWorkflowContractTest.php | 21 + .../CiHeavyBrowserWorkflowContractTest.php | 45 ++ ...iLaneFailureClassificationContractTest.php | 73 ++++ .../Guards/ConfidenceLaneContractTest.php | 14 + .../Guards/FastFeedbackLaneContractTest.php | 16 +- .../HeavyGovernanceLaneContractTest.php | 14 + .../Guards/TestLaneArtifactsContractTest.php | 69 +++- .../Guards/TestLaneCommandContractTest.php | 38 +- .../Feature/Guards/TestLaneManifestTest.php | 39 +- .../platform/tests/Support/TestLaneBudget.php | 153 +++++++ .../tests/Support/TestLaneManifest.php | 388 +++++++++++++++++- .../platform/tests/Support/TestLaneReport.php | 348 ++++++++++++++-- scripts/platform-test-artifacts | 73 ++++ scripts/platform-test-lane | 20 + scripts/platform-test-report | 22 +- .../checklists/requirements.md | 39 ++ .../ci-lane-governance.logical.openapi.yaml | 317 ++++++++++++++ .../contracts/ci-lane-matrix.schema.json | 383 +++++++++++++++++ .../data-model.md | 173 ++++++++ .../210-ci-matrix-budget-enforcement/plan.md | 188 +++++++++ .../quickstart.md | 108 +++++ .../research.md | 65 +++ .../210-ci-matrix-budget-enforcement/spec.md | 236 +++++++++++ .../210-ci-matrix-budget-enforcement/tasks.md | 202 +++++++++ 38 files changed, 3413 insertions(+), 60 deletions(-) create mode 100644 .gitea/workflows/test-browser.yml create mode 100644 .gitea/workflows/test-heavy-governance.yml create mode 100644 .gitea/workflows/test-main-confidence.yml create mode 100644 .gitea/workflows/test-pr-fast-feedback.yml create mode 100644 apps/platform/tests/Feature/Guards/CiConfidenceWorkflowContractTest.php create mode 100644 apps/platform/tests/Feature/Guards/CiFastFeedbackWorkflowContractTest.php create mode 100644 apps/platform/tests/Feature/Guards/CiHeavyBrowserWorkflowContractTest.php create mode 100644 apps/platform/tests/Feature/Guards/CiLaneFailureClassificationContractTest.php create mode 100755 scripts/platform-test-artifacts create mode 100644 specs/210-ci-matrix-budget-enforcement/checklists/requirements.md create mode 100644 specs/210-ci-matrix-budget-enforcement/contracts/ci-lane-governance.logical.openapi.yaml create mode 100644 specs/210-ci-matrix-budget-enforcement/contracts/ci-lane-matrix.schema.json create mode 100644 specs/210-ci-matrix-budget-enforcement/data-model.md create mode 100644 specs/210-ci-matrix-budget-enforcement/plan.md create mode 100644 specs/210-ci-matrix-budget-enforcement/quickstart.md create mode 100644 specs/210-ci-matrix-budget-enforcement/research.md create mode 100644 specs/210-ci-matrix-budget-enforcement/spec.md create mode 100644 specs/210-ci-matrix-budget-enforcement/tasks.md diff --git a/.gitea/workflows/test-browser.yml b/.gitea/workflows/test-browser.yml new file mode 100644 index 00000000..3721cc9f --- /dev/null +++ b/.gitea/workflows/test-browser.yml @@ -0,0 +1,74 @@ +name: Browser Lane + +on: + workflow_dispatch: + schedule: + - cron: '43 4 * * 1-5' + +jobs: + browser: + if: ${{ github.event_name != 'schedule' || vars.TENANTATLAS_ENABLE_BROWSER_SCHEDULE == '1' }} + runs-on: ubuntu-latest + env: + SAIL_TTY: 'false' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install platform dependencies + run: | + cd apps/platform + if [[ ! -f .env ]]; then + cp .env.example .env + fi + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Boot Sail + run: | + cd apps/platform + ./vendor/bin/sail up -d + ./vendor/bin/sail artisan key:generate --force --no-interaction + + - name: Resolve Browser context + id: context + run: | + if [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "workflow_id=browser-scheduled" >> "$GITHUB_OUTPUT" + echo "trigger_class=scheduled" >> "$GITHUB_OUTPUT" + else + echo "workflow_id=browser-manual" >> "$GITHUB_OUTPUT" + echo "trigger_class=manual" >> "$GITHUB_OUTPUT" + fi + + - name: Run Browser lane + run: ./scripts/platform-test-lane browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Refresh Browser report + if: always() + run: ./scripts/platform-test-report browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Stage Browser artifacts + if: always() + run: ./scripts/platform-test-artifacts browser .gitea-artifacts/browser --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Upload Browser artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: browser-artifacts + path: .gitea-artifacts/browser + if-no-files-found: error + + - name: Stop Sail + if: always() + run: | + cd apps/platform + ./vendor/bin/sail stop \ No newline at end of file diff --git a/.gitea/workflows/test-heavy-governance.yml b/.gitea/workflows/test-heavy-governance.yml new file mode 100644 index 00000000..ec3c1623 --- /dev/null +++ b/.gitea/workflows/test-heavy-governance.yml @@ -0,0 +1,74 @@ +name: Heavy Governance Lane + +on: + workflow_dispatch: + schedule: + - cron: '17 4 * * 1-5' + +jobs: + heavy-governance: + if: ${{ github.event_name != 'schedule' || vars.TENANTATLAS_ENABLE_HEAVY_GOVERNANCE_SCHEDULE == '1' }} + runs-on: ubuntu-latest + env: + SAIL_TTY: 'false' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install platform dependencies + run: | + cd apps/platform + if [[ ! -f .env ]]; then + cp .env.example .env + fi + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Boot Sail + run: | + cd apps/platform + ./vendor/bin/sail up -d + ./vendor/bin/sail artisan key:generate --force --no-interaction + + - name: Resolve Heavy Governance context + id: context + run: | + if [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "workflow_id=heavy-governance-scheduled" >> "$GITHUB_OUTPUT" + echo "trigger_class=scheduled" >> "$GITHUB_OUTPUT" + else + echo "workflow_id=heavy-governance-manual" >> "$GITHUB_OUTPUT" + echo "trigger_class=manual" >> "$GITHUB_OUTPUT" + fi + + - name: Run Heavy Governance lane + run: ./scripts/platform-test-lane heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Refresh Heavy Governance report + if: always() + run: ./scripts/platform-test-report heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Stage Heavy Governance artifacts + if: always() + run: ./scripts/platform-test-artifacts heavy-governance .gitea-artifacts/heavy-governance --workflow-id=${{ steps.context.outputs.workflow_id }} --trigger-class=${{ steps.context.outputs.trigger_class }} + + - name: Upload Heavy Governance artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: heavy-governance-artifacts + path: .gitea-artifacts/heavy-governance + if-no-files-found: error + + - name: Stop Sail + if: always() + run: | + cd apps/platform + ./vendor/bin/sail stop \ No newline at end of file diff --git a/.gitea/workflows/test-main-confidence.yml b/.gitea/workflows/test-main-confidence.yml new file mode 100644 index 00000000..62a9bb01 --- /dev/null +++ b/.gitea/workflows/test-main-confidence.yml @@ -0,0 +1,62 @@ +name: Main Confidence + +on: + push: + branches: + - dev + +jobs: + confidence: + runs-on: ubuntu-latest + env: + SAIL_TTY: 'false' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install platform dependencies + run: | + cd apps/platform + if [[ ! -f .env ]]; then + cp .env.example .env + fi + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Boot Sail + run: | + cd apps/platform + ./vendor/bin/sail up -d + ./vendor/bin/sail artisan key:generate --force --no-interaction + + - name: Run Confidence lane + run: ./scripts/platform-test-lane confidence --workflow-id=main-confidence --trigger-class=mainline-push + + - name: Refresh Confidence report + if: always() + run: ./scripts/platform-test-report confidence --workflow-id=main-confidence --trigger-class=mainline-push + + - name: Stage Confidence artifacts + if: always() + run: ./scripts/platform-test-artifacts confidence .gitea-artifacts/main-confidence --workflow-id=main-confidence --trigger-class=mainline-push + + - name: Upload Confidence artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: confidence-artifacts + path: .gitea-artifacts/main-confidence + if-no-files-found: error + + - name: Stop Sail + if: always() + run: | + cd apps/platform + ./vendor/bin/sail stop \ No newline at end of file diff --git a/.gitea/workflows/test-pr-fast-feedback.yml b/.gitea/workflows/test-pr-fast-feedback.yml new file mode 100644 index 00000000..b66eed72 --- /dev/null +++ b/.gitea/workflows/test-pr-fast-feedback.yml @@ -0,0 +1,64 @@ +name: PR Fast Feedback + +on: + pull_request: + types: + - opened + - reopened + - synchronize + +jobs: + fast-feedback: + runs-on: ubuntu-latest + env: + SAIL_TTY: 'false' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + coverage: none + tools: composer:v2 + + - name: Install platform dependencies + run: | + cd apps/platform + if [[ ! -f .env ]]; then + cp .env.example .env + fi + composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Boot Sail + run: | + cd apps/platform + ./vendor/bin/sail up -d + ./vendor/bin/sail artisan key:generate --force --no-interaction + + - name: Run Fast Feedback lane + run: ./scripts/platform-test-lane fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request + + - name: Refresh Fast Feedback report + if: always() + run: ./scripts/platform-test-report fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request + + - name: Stage Fast Feedback artifacts + if: always() + run: ./scripts/platform-test-artifacts fast-feedback .gitea-artifacts/pr-fast-feedback --workflow-id=pr-fast-feedback --trigger-class=pull-request + + - name: Upload Fast Feedback artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: fast-feedback-artifacts + path: .gitea-artifacts/pr-fast-feedback + if-no-files-found: error + + - name: Stop Sail + if: always() + run: | + cd apps/platform + ./vendor/bin/sail stop \ No newline at end of file diff --git a/.github/agents/copilot-instructions.md b/.github/agents/copilot-instructions.md index 76d9f0f0..89544d81 100644 --- a/.github/agents/copilot-instructions.md +++ b/.github/agents/copilot-instructions.md @@ -196,6 +196,8 @@ ## Active Technologies - SQLite `:memory:` for the default test environment, isolated PostgreSQL coverage via the existing dedicated suite, and lane-measurement artifacts under the app-root contract path `storage/logs/test-lanes` (207-shared-test-fixture-slimming) - SQLite `:memory:` for the default test environment, existing lane artifacts under the app-root contract path `storage/logs/test-lanes`, and no new product persistence (208-heavy-suite-segmentation) - SQLite `:memory:` for the default test environment, mixed database strategy for some heavy-governance families as declared in `TestLaneManifest`, and existing lane artifacts under the app-root contract path `storage/logs/test-lanes` (209-heavy-governance-cost) +- PHP 8.4.15 for repo-truth test governance, Bash for repo-root wrappers, and GitHub-compatible Gitea Actions workflow YAML under `.gitea/workflows/` + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail, Gitea Actions backed by `act_runner`, and the existing `Tests\Support\TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams (210-ci-matrix-budget-enforcement) +- SQLite `:memory:` for default lane execution, filesystem artifacts under the app-root contract path `storage/logs/test-lanes`, checked-in workflow YAML under `.gitea/workflows/`, and no new product database persistence (210-ci-matrix-budget-enforcement) - PHP 8.4.15 (feat/005-bulk-operations) @@ -230,8 +232,8 @@ ## Code Style PHP 8.4.15: Follow standard conventions ## Recent Changes +- 210-ci-matrix-budget-enforcement: Added PHP 8.4.15 for repo-truth test governance, Bash for repo-root wrappers, and GitHub-compatible Gitea Actions workflow YAML under `.gitea/workflows/` + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail, Gitea Actions backed by `act_runner`, and the existing `Tests\Support\TestLaneManifest`, `TestLaneBudget`, and `TestLaneReport` seams - 209-heavy-governance-cost: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail - 208-heavy-suite-segmentation: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail -- 207-shared-test-fixture-slimming: Added PHP 8.4.15 + Laravel 12, Pest v4, PHPUnit 12, Filament v5, Livewire v4, Laravel Sail diff --git a/.specify/README.md b/.specify/README.md index e56f1777..b027499c 100644 --- a/.specify/README.md +++ b/.specify/README.md @@ -10,5 +10,6 @@ ## Important - `plan.md` - `tasks.md` - `checklists/requirements.md` +- Runtime-changing work MUST carry testing/lane/runtime impact through the active `spec.md`, `plan.md`, and `tasks.md`; lane upkeep belongs to the feature, not to a later cleanup pass. The files `.specify/spec.md`, `.specify/plan.md`, `.specify/tasks.md` may exist as legacy references only. diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index f19c6bef..bdad93f9 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,37 +1,32 @@ # TenantPilot Constitution @@ -107,6 +102,13 @@ ### Tests Must Protect Business Truth (TEST-TRUTH-001) - Large dedicated test surfaces for thin presentation indirection SHOULD be avoided. - If a pattern creates more test burden than product certainty, the pattern SHOULD be simplified. +### Test Suite Governance Must Live In The Delivery Workflow (TEST-GOV-001) +- Test-suite governance is a standing workflow rule, not an occasional cleanup project. +- Every runtime-changing spec MUST declare the affected validation lane(s), any fixture/helper cost risk, whether it introduces or expands heavy-governance or browser coverage, and whether budget/baseline follow-up is needed. +- Plans MUST choose the narrowest lane mix that proves the change and MUST call out new heavy families, expensive defaults, or CI/runtime drift before implementation starts. +- Tasks and reviews MUST confirm lane classification, keep default fixtures cheap, reject accidental heavy promotion, and record material runtime drift or recalibration work in the active spec or PR. +- Standalone follow-up specs for test governance are reserved for recurring pain or structural lane changes; ordinary recalibration belongs inside normal delivery work. + ### Enterprise Complexity Is Allowed Only Where Risk Demands It (RISK-COMP-001) - Heavier architecture is explicitly legitimate for workspace or tenant isolation, RBAC and policy enforcement, auditability, immutable history and snapshot truth, queue/job execution legitimacy, provider credential safety, retention/compliance evidence, and operator-critical lifecycle correctness. - Badge systems, explanation builders, trust/confidence overlays, presentation taxonomies, generic provider frameworks without real provider variance, speculative export/report/review infrastructure, UI meta-governance frameworks, and derived helper entities promoted into persisted truth are high-risk overproduction zones and require extra restraint. @@ -1326,6 +1328,7 @@ ### Spec-First Workflow ## Quality Gates - Changes MUST be programmatically tested (Pest) and run via targeted `php artisan test ...`. +- Runtime changes MUST validate the narrowest relevant lane and document any material budget, baseline, or trend follow-up in the active spec or PR. - Run `./vendor/bin/sail bin pint --dirty` before finalizing. ## Governance @@ -1334,9 +1337,11 @@ ### Scope, Compliance, and Review Expectations - This constitution applies across the repo. Feature specs may add stricter constraints but not weaker ones. - Restore semantics changes require: spec update, checklist update (if applicable), and tests proving safety. - Specs and PRs that introduce new persisted truth, abstractions, states, DTO/presenter layers, or taxonomies MUST include the proportionality review required by BLOAT-001. +- Runtime-changing specs and PRs MUST include testing/lane/runtime impact covering affected lanes, fixture/helper cost changes, any heavy-family expansion, expected budget/baseline effect, and the minimal validation commands. - Specs and PRs that change operator-facing surfaces MUST classify each affected surface under DECIDE-001 and justify any new Primary Decision Surface or workflow-first navigation change. +- Reviews MUST reject runtime changes when lane classification is missing, expensive defaults are introduced silently, or material CI/runtime drift is left undocumented. - Review and approval MUST favor simplification, replacement, and absorption over additive semantic layering. - Future-release preparation alone is not sufficient justification for new persistence or frameworkization unless security, tenant isolation, auditability, compliance evidence, or queue correctness already require it. @@ -1350,4 +1355,4 @@ ### Versioning Policy (SemVer) - **MINOR**: new principle/section or materially expanded guidance. - **MAJOR**: removing/redefining principles in a backward-incompatible way. -**Version**: 2.3.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-12 +**Version**: 2.4.0 | **Ratified**: 2026-01-03 | **Last Amended**: 2026-04-17 diff --git a/.specify/templates/checklist-template.md b/.specify/templates/checklist-template.md index 806657da..42bfb263 100644 --- a/.specify/templates/checklist-template.md +++ b/.specify/templates/checklist-template.md @@ -5,6 +5,7 @@ # [CHECKLIST TYPE] Checklist: [FEATURE NAME] **Feature**: [Link to spec.md or relevant documentation] **Note**: This checklist is generated by the `/speckit.checklist` command based on feature context and requirements. +If the checklist covers runtime behavior changes, include lane classification, fixture-cost review, heavy-family justification, minimal validation commands, and any budget/baseline follow-up checks.