jenkins improvements

This commit is contained in:
bQUARKz 2026-04-21 17:37:07 +01:00
parent 401a56a4c6
commit 15e1e2321e
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 209 additions and 93 deletions

View File

@ -1,4 +1,4 @@
.PHONY: fmt fmt-check clippy test-local test-debugger-socket clean coverage coverage-xml coverage-json coverage-report-json coverage-domain-list coverage-domain-evidence test ci cobertura .PHONY: fmt fmt-check clippy test-local test-debugger-socket clean coverage coverage-xml coverage-json coverage-report-json coverage-domain-list coverage-domain-evidence coverage-domain-gate test ci ci-domains cobertura
fmt: fmt:
cargo fmt cargo fmt
@ -40,6 +40,10 @@ coverage-domain-evidence:
fi fi
./scripts/coverage-domain-evidence.sh "$(DOMAIN)" ./scripts/coverage-domain-evidence.sh "$(DOMAIN)"
coverage-domain-gate:
./scripts/coverage-domain-evidence.sh --check-all
test: fmt-check clippy test-local test-debugger-socket test: fmt-check clippy test-local test-debugger-socket
ci: clean fmt-check clippy coverage coverage-report-json ci: clean fmt-check clippy coverage coverage-report-json
ci-domains: clean fmt-check clippy coverage coverage-report-json coverage-xml coverage-json coverage-domain-gate
cobertura: coverage-xml coverage-json cobertura: coverage-xml coverage-json

View File

@ -83,7 +83,7 @@ The desktop runtime opens a native window through the host layer, so this last c
The repository uses a two-layer runtime-edge coverage model: The repository uses a two-layer runtime-edge coverage model:
- a mandatory global workspace coverage gate in CI; - a mandatory CI gate based on canonical runtime-edge domains;
- domain-oriented review evidence for the canonical runtime-edge domains. - domain-oriented review evidence for the canonical runtime-edge domains.
Current canonical domains: Current canonical domains:
@ -99,6 +99,7 @@ Global coverage artifacts:
```bash ```bash
make ci make ci
make cobertura make cobertura
make ci-domains
``` ```
This produces: This produces:
@ -119,6 +120,10 @@ make coverage-domain-evidence DOMAIN=host-dependent
Per-domain baselines currently start around `60`. Per-domain baselines currently start around `60`.
That does not waive test obligations. It means the project is adopting explicit domain evidence immediately while tightening quantitative expectations incrementally over time. That does not waive test obligations. It means the project is adopting explicit domain evidence immediately while tightening quantitative expectations incrementally over time.
`make ci-domains` is the CI entrypoint used by Jenkins.
It runs the standard coverage pipeline, produces the HTML/XML/JSON artifacts, and fails when any canonical domain falls below its configured `baseline_percent`.
The CI gate currently compares domain `lines` coverage against each domain baseline.
When a PR changes the observable contract of a canonical domain, it is expected to: When a PR changes the observable contract of a canonical domain, it is expected to:
- add or adjust tests for that domain; or - add or adjust tests for that domain; or

View File

@ -4,7 +4,7 @@
{"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]} {"type":"discussion","id":"DSC-0021","status":"done","ticket":"asset-entry-codec-enum-with-metadata","title":"Asset Entry Codec Enum Contract","created_at":"2026-04-09","updated_at":"2026-04-09","tags":["asset","runtime","codec","metadata"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0024","file":"lessons/DSC-0021-asset-entry-codec-enum-contract/LSN-0024-string-on-the-wire-enum-in-runtime.md","status":"done","created_at":"2026-04-09","updated_at":"2026-04-09"}]}
{"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]} {"type":"discussion","id":"DSC-0022","status":"done","ticket":"tile-bank-vs-glyph-bank-domain-naming","title":"Glyph Bank Domain Naming Contract","created_at":"2026-04-09","updated_at":"2026-04-10","tags":["gfx","runtime","naming","domain-model"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0025","file":"lessons/DSC-0022-glyph-bank-domain-naming/LSN-0025-rename-artifact-by-meaning-not-by-token.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
{"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]} {"type":"discussion","id":"DSC-0001","status":"done","ticket":"legacy-runtime-learn-import","title":"Import legacy runtime learn into discussion lessons","created_at":"2026-03-27","updated_at":"2026-03-27","tags":["migration","tech-debt"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0001","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0001-prometeu-learn-index.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0002","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0002-historical-asset-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0003","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0003-historical-audio-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0004","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0004-historical-cartridge-boot-protocol-and-manifest-authority.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0005","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0005-historical-game-memcard-slots-surface-and-semantics.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0006","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0006-historical-gfx-status-first-fault-and-return-contract.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0007","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0007-historical-retired-fault-and-input-decisions.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0008","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0008-historical-vm-core-and-assets.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0009","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0009-mental-model-asset-management.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0010","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0010-mental-model-audio.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0011","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0011-mental-model-gfx.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0012","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0012-mental-model-input.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0013","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0013-mental-model-observability-and-debugging.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0014","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0014-mental-model-portability-and-cross-platform.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0015","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0015-mental-model-save-memory-and-memcard.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0016","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0016-mental-model-status-first-and-fault-thinking.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0017","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0017-mental-model-time-and-cycles.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"},{"id":"LSN-0018","file":"lessons/DSC-0001-runtime-learn-legacy-import/LSN-0018-mental-model-touch.md","status":"done","created_at":"2026-03-27","updated_at":"2026-03-27"}]}
{"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-20","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0020","file":"workflow/decisions/DEC-0020-runtime-edge-coverage-governance-by-domain.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-20","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0037","file":"workflow/plans/PLN-0037-runtime-edge-coverage-governance-foundation.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0038","file":"workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0039","file":"workflow/plans/PLN-0039-incremental-runtime-domain-suite-split-and-baselines.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]}],"lessons":[]} {"type":"discussion","id":"DSC-0002","status":"open","ticket":"runtime-edge-test-plan","title":"Agenda - Runtime Edge Test Plan","created_at":"2026-03-27","updated_at":"2026-04-21","tags":[],"agendas":[{"id":"AGD-0001","file":"workflow/agendas/AGD-0001-runtime-edge-test-plan.md","status":"done","created_at":"2026-03-27","updated_at":"2026-04-20"}],"decisions":[{"id":"DEC-0020","file":"workflow/decisions/DEC-0020-runtime-edge-coverage-governance-by-domain.md","status":"in_progress","created_at":"2026-04-20","updated_at":"2026-04-21","ref_agenda":"AGD-0001"}],"plans":[{"id":"PLN-0037","file":"workflow/plans/PLN-0037-runtime-edge-coverage-governance-foundation.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-21","ref_decisions":["DEC-0020"]},{"id":"PLN-0038","file":"workflow/plans/PLN-0038-firmware-and-host-dependent-domain-coverage-expansion.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]},{"id":"PLN-0039","file":"workflow/plans/PLN-0039-incremental-runtime-domain-suite-split-and-baselines.md","status":"done","created_at":"2026-04-20","updated_at":"2026-04-20","ref_decisions":["DEC-0020"]}],"lessons":[]}
{"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0003","status":"open","ticket":"packed-cartridge-loader-pmc","title":"Agenda - Packed Cartridge Loader PMC","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0002","file":"workflow/agendas/AGD-0002-packed-cartridge-loader-pmc.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"workflow/agendas/AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0004","status":"open","ticket":"system-run-cart","title":"Agenda - System Run Cart","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0003","file":"workflow/agendas/AGD-0003-system-run-cart.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}
{"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]} {"type":"discussion","id":"DSC-0005","status":"open","ticket":"system-fault-semantics-and-control-surface","title":"Agenda - System Fault Semantics and Control Surface","created_at":"2026-03-27","updated_at":"2026-03-27","tags":[],"agendas":[{"id":"AGD-0004","file":"workflow/agendas/AGD-0004-system-fault-semantics-and-control-surface.md","status":"open","created_at":"2026-03-27","updated_at":"2026-03-27"}],"decisions":[],"plans":[],"lessons":[]}

View File

@ -26,7 +26,7 @@ The runtime edge is no longer broadly untested. The repository now already conta
The remaining problem is governance, not raw test absence. The remaining problem is governance, not raw test absence.
Global coverage metrics already exist through `cargo llvm-cov`, and CI already enforces a workspace-wide minimum gate. That is useful, but insufficient on its own. A good aggregated number does not guarantee that firmware transitions, boot flow, host-dependent surfaces, or domain-specific status-first contracts are covered at the right level. Global coverage metrics already exist through `cargo llvm-cov`. A single aggregated workspace gate proved useful, but insufficient on its own. A good aggregated number does not guarantee that firmware transitions, boot flow, host-dependent surfaces, or domain-specific status-first contracts are covered at the right level.
The project therefore needs an explicit domain-based acceptance model for tests: The project therefore needs an explicit domain-based acceptance model for tests:
@ -37,9 +37,9 @@ The project therefore needs an explicit domain-based acceptance model for tests:
## Decisao ## Decisao
PROMETEU SHALL govern runtime-edge test sufficiency through a two-layer model: PROMETEU SHALL govern runtime-edge test sufficiency through a domain-first model:
1. a mandatory global coverage gate enforced in CI; and 1. a mandatory CI gate enforced from the canonical runtime-edge domains; and
2. a domain-based acceptance gate driven by mandatory scenarios plus domain-scoped coverage evidence. 2. a domain-based acceptance gate driven by mandatory scenarios plus domain-scoped coverage evidence.
The canonical domains SHALL be: The canonical domains SHALL be:
@ -61,6 +61,7 @@ No PR that changes the observable contract of a canonical domain SHALL be accept
- an explicit justification that the observable contract did not change. - an explicit justification that the observable contract did not change.
Improved aggregate coverage SHALL NOT be accepted as sufficient evidence on its own when the changed behavior belongs to a canonical domain with mandatory scenarios. Improved aggregate coverage SHALL NOT be accepted as sufficient evidence on its own when the changed behavior belongs to a canonical domain with mandatory scenarios.
Workspace-wide aggregate coverage MAY still be generated as a report artifact, but it SHALL NOT be the authoritative CI acceptance gate.
## Rationale ## Rationale
@ -100,11 +101,12 @@ The correct role of coverage is supporting evidence, not sole authority.
## Invariantes / Contrato ## Invariantes / Contrato
### Global Coverage Contract ### CI Coverage Contract
- CI MUST keep a workspace-wide coverage gate. - CI MUST enforce coverage from the canonical runtime-edge domains instead of a workspace-wide aggregate percentage.
- The current `llvm-cov` based global gate remains valid as the operational baseline. - The current `llvm-cov` toolchain remains valid as the operational baseline.
- Changing the tooling is out of scope for this decision. - Changing the tooling is out of scope for this decision.
- The authoritative quantitative CI gate is each domain `baseline_percent`, evaluated against domain `lines` coverage.
### Domain Gate Contract ### Domain Gate Contract
@ -188,13 +190,15 @@ Mandatory scenarios:
### Tooling / CI ### Tooling / CI
- Existing `llvm-cov` and Jenkins integration remain valid. - Existing `llvm-cov` and Jenkins integration remain valid.
- Future plans may add domain reporting, thresholds, or helper commands without changing this baseline decision. - Jenkins MUST call repository helpers that compute and enforce the domain coverage gate.
- Workspace aggregate reports may continue to be published for inspection, but they are not the merge gate.
## Referencias ## Referencias
- `AGD-0001` - `AGD-0001`
- `Makefile` - `Makefile`
- `files/config/Jenkinsfile` - `files/config/Jenkinsfile`
- `files/config/runtime-edge-coverage-domains.json`
- `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md` - `docs/specs/runtime/12-firmware-pos-and-prometeuhub.md`
- `crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs` - `crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs`
- `crates/console/prometeu-system/src/services/fs/virtual_fs.rs` - `crates/console/prometeu-system/src/services/fs/virtual_fs.rs`
@ -202,8 +206,8 @@ Mandatory scenarios:
## Propagacao Necessaria ## Propagacao Necessaria
- Create an execution plan that turns the domain model into concrete work items. - Create or revise execution plans that turn the domain model into concrete CI and review work items.
- Define how domain evidence will be gathered during review. - Define how domain evidence will be gathered during review.
- Decide whether to add helper commands or scripts for domain-oriented coverage inspection. - Keep helper commands or scripts as the canonical interface for domain-oriented coverage inspection and CI enforcement.
- Prioritize firmware and host-dependent expansion first. - Prioritize firmware and host-dependent expansion first.
- Define an incremental split strategy for the monolithic runtime test suite. - Define an incremental split strategy for the monolithic runtime test suite.

View File

@ -21,7 +21,7 @@ Its goal is to turn the decision into concrete review and CI mechanics without c
Define the repository-level mechanics for: Define the repository-level mechanics for:
- global coverage gate continuity; - domain coverage gate enforcement in CI;
- domain-scoped coverage evidence during review; - domain-scoped coverage evidence during review;
- baseline tracking that can start at `0` per domain and tighten later; - baseline tracking that can start at `0` per domain and tighten later;
- reviewer-visible rules for when domain tests are mandatory. - reviewer-visible rules for when domain tests are mandatory.
@ -29,7 +29,7 @@ Define the repository-level mechanics for:
## Escopo ## Escopo
- Add repository commands or scripts for domain-oriented coverage evidence collection. - Add repository commands or scripts for domain-oriented coverage evidence collection.
- Add repository-facing documentation that explains the global gate plus domain evidence workflow. - Add repository-facing documentation that explains the domain gate plus domain evidence workflow.
- Align CI/coverage outputs with the new governance model without replacing `llvm-cov`. - Align CI/coverage outputs with the new governance model without replacing `llvm-cov`.
## Fora de Escopo ## Fora de Escopo
@ -51,7 +51,8 @@ Add one repository-visible command path that produces:
- existing global HTML/XML/JSON coverage artifacts; - existing global HTML/XML/JSON coverage artifacts;
- a documented procedure for mapping changed files/modules to one of the canonical domains; - a documented procedure for mapping changed files/modules to one of the canonical domains;
- an initial baseline format that records per-domain coverage starting from `0`. - an initial baseline format that records per-domain coverage starting from `0`;
- a CI-friendly command that fails when a domain baseline is not met.
**File(s):** **File(s):**
- `Makefile` - `Makefile`
@ -68,7 +69,7 @@ Prefer simple wrappers over new tooling. For example:
- capture `llvm-cov` JSON/HTML consistently; - capture `llvm-cov` JSON/HTML consistently;
- filter or summarize target files/modules for a domain; - filter or summarize target files/modules for a domain;
- emit review-friendly evidence artifacts without changing the authoritative global gate. - emit review-friendly evidence artifacts while also enforcing the authoritative domain gate.
**File(s):** **File(s):**
- `Makefile` - `Makefile`
@ -86,6 +87,7 @@ Document that any PR touching a canonical domain must either:
- justify why the observable contract did not change. - justify why the observable contract did not change.
Document that domain coverage evidence is mandatory review input even when domain thresholds are still at baseline `0`. Document that domain coverage evidence is mandatory review input even when domain thresholds are still at baseline `0`.
Document that Jenkins consumes the repository helper target directly instead of re-implementing the gate inline.
**File(s):** **File(s):**
- `README.md` - `README.md`
@ -94,10 +96,10 @@ Document that domain coverage evidence is mandatory review input even when domai
### Step 4 - Align CI outputs with the governance model ### Step 4 - Align CI outputs with the governance model
**What:** **What:**
Make the existing coverage pipeline clearly compatible with domain review. Make the existing coverage pipeline clearly compatible with domain review and domain-based CI failure.
**How:** **How:**
Keep the current global Jenkins gate unchanged while ensuring produced artifacts are sufficient for domain inspection. Make Jenkins call a repository helper target that runs the coverage pipeline, produces the existing artifacts, and fails from domain baselines.
If needed, archive additional summaries or helper outputs generated by the new scripts. If needed, archive additional summaries or helper outputs generated by the new scripts.
**File(s):** **File(s):**
@ -106,10 +108,10 @@ If needed, archive additional summaries or helper outputs generated by the new s
## Criterios de Aceite ## Criterios de Aceite
- [ ] The repository exposes a documented way to collect domain-oriented coverage evidence without replacing the global `llvm-cov` gate. - [ ] The repository exposes a documented way to collect domain-oriented coverage evidence and enforce domain baselines in CI.
- [ ] Review guidance explicitly states when domain tests are mandatory. - [ ] Review guidance explicitly states when domain tests are mandatory.
- [ ] Domain baselines are represented in a form that can start at `0` and increase later. - [ ] Domain baselines are represented in a form that can start at `0` and increase later.
- [ ] Existing global coverage enforcement remains intact. - [ ] Jenkins uses a repository-owned helper target for the coverage gate.
## Tests / Validacao ## Tests / Validacao
@ -122,9 +124,10 @@ If needed, archive additional summaries or helper outputs generated by the new s
- Show that the global coverage artifacts still build. - Show that the global coverage artifacts still build.
- Show an example of domain evidence collection for at least one domain. - Show an example of domain evidence collection for at least one domain.
- Show that the domain gate fails when a baseline is missed.
## Riscos ## Riscos
- Overdesigning domain evidence collection before real reviewer usage patterns exist. - Overdesigning domain evidence collection before real reviewer usage patterns exist.
- Accidentally creating a second competing coverage authority instead of a reviewer aid. - Letting Jenkins re-encode policy logic instead of consuming the repository helper target.
- Spreading policy text across too many documents and making the rule hard to find. - Spreading policy text across too many documents and making the rule hard to find.

View File

@ -6,10 +6,6 @@ pipeline {
CARGO_TARGET_DIR = 'target' CARGO_TARGET_DIR = 'target'
CARGO_TERM_COLOR = 'always' CARGO_TERM_COLOR = 'always'
MIN_LINES = '60'
MIN_FUNCTIONS = '60'
MIN_REGIONS = '60'
} }
stages { stages {
@ -17,45 +13,12 @@ pipeline {
steps { steps {
sh ''' sh '''
set -eux set -eux
make ci cobertura make ci-domains
LINES=$(jq -r '.data[0].totals.lines.percent' target/llvm-cov/summary.json)
FUNCTIONS=$(jq -r '.data[0].totals.functions.percent' target/llvm-cov/summary.json)
REGIONS=$(jq -r '.data[0].totals.regions.percent' target/llvm-cov/summary.json)
echo "Coverage summary:"
echo " Lines: ${LINES}%"
echo " Functions: ${FUNCTIONS}%"
echo " Regions: ${REGIONS}%"
FAIL=0
awk "BEGIN { exit !(${LINES} < ${MIN_LINES}) }" && {
echo "Lines coverage ${LINES}% is below minimum ${MIN_LINES}%"
FAIL=1
} || true
awk "BEGIN { exit !(${FUNCTIONS} < ${MIN_FUNCTIONS}) }" && {
echo "Functions coverage ${FUNCTIONS}% is below minimum ${MIN_FUNCTIONS}%"
FAIL=1
} || true
awk "BEGIN { exit !(${REGIONS} < ${MIN_REGIONS}) }" && {
echo "Regions coverage ${REGIONS}% is below minimum ${MIN_REGIONS}%"
FAIL=1
} || true
if [ "$FAIL" -ne 0 ]; then
echo "Coverage gate failed."
exit 1
fi
echo "Coverage gate passed."
''' '''
// withChecks(name: 'Test', includeStage: true) { // withChecks(name: 'Test', includeStage: true) {
// sh ''' // sh '''
// set -eux // set -eux
// make ci cobertura // make ci-domains
// ''' // '''
// recordCoverage( // recordCoverage(
// tools: [[parser: 'COBERTURA', pattern: 'target/llvm-cov/cobertura.xml']], // tools: [[parser: 'COBERTURA', pattern: 'target/llvm-cov/cobertura.xml']],

View File

@ -2,7 +2,7 @@
"version": 1, "version": 1,
"domains": { "domains": {
"system/runtime": { "system/runtime": {
"baseline_percent": 66, "baseline_percent": 68,
"paths": [ "paths": [
"crates/console/prometeu-system/src/virtual_machine_runtime.rs", "crates/console/prometeu-system/src/virtual_machine_runtime.rs",
"crates/console/prometeu-system/src/virtual_machine_runtime/" "crates/console/prometeu-system/src/virtual_machine_runtime/"
@ -16,7 +16,7 @@
] ]
}, },
"asset/bank": { "asset/bank": {
"baseline_percent": 80, "baseline_percent": 86,
"paths": [ "paths": [
"crates/console/prometeu-hal/src/asset", "crates/console/prometeu-hal/src/asset",
"crates/console/prometeu-hal/src/asset.rs", "crates/console/prometeu-hal/src/asset.rs",
@ -27,7 +27,7 @@
] ]
}, },
"firmware": { "firmware": {
"baseline_percent": 74, "baseline_percent": 76,
"paths": [ "paths": [
"crates/console/prometeu-firmware/src/" "crates/console/prometeu-firmware/src/"
] ]

View File

@ -4,12 +4,37 @@ set -euo pipefail
ROOT="$(git rev-parse --show-toplevel)" ROOT="$(git rev-parse --show-toplevel)"
CONFIG_PATH="${ROOT}/files/config/runtime-edge-coverage-domains.json" CONFIG_PATH="${ROOT}/files/config/runtime-edge-coverage-domains.json"
DEFAULT_REPORT="${ROOT}/target/llvm-cov/report.json" DEFAULT_REPORT="${ROOT}/target/llvm-cov/report.json"
GREEN=$'\033[32m'
RED=$'\033[31m'
CYAN=$'\033[36m'
RESET=$'\033[0m'
line_percent() {
jq -r --arg domain "${1}" --arg root "${ROOT}" '
.domains[$domain].paths as $paths
| def matches_domain:
. as $file
| any($paths[]; . as $p | ($file.filename | startswith($root + "/" + $p) or startswith("./" + $p) or startswith($p)));
def pct($covered; $count):
if $count > 0 then (($covered * 10000 / $count | floor) / 100) else 0 end;
reduce (
input.data[]?.files[]?
| select(matches_domain)
) as $file (
{lines: {count: 0, covered: 0}};
.lines.count += ($file.summary.lines.count // 0)
| .lines.covered += ($file.summary.lines.covered // 0)
)
| pct(.lines.covered; .lines.count)
' "${CONFIG_PATH}" "${2}"
}
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: Usage:
coverage-domain-evidence.sh --list coverage-domain-evidence.sh --list
coverage-domain-evidence.sh <domain> [report.json] coverage-domain-evidence.sh [--check] [--show-summary-details] [--show-paths] [--show-tracked-files] [--show-matched-files] [--verbose] <domain> [report.json]
coverage-domain-evidence.sh [--show-summary-details] [--show-paths] [--show-tracked-files] [--show-matched-files] [--verbose] --check-all [report.json]
The report path defaults to target/llvm-cov/report.json and is expected to be The report path defaults to target/llvm-cov/report.json and is expected to be
generated by `make ci` or `make coverage-report-json`. generated by `make ci` or `make coverage-report-json`.
@ -31,9 +56,87 @@ if [[ "$1" == "--list" ]]; then
exit 0 exit 0
fi fi
DOMAIN="$1" CHECK_MODE=0
CHECK_ALL=0
SHOW_PATHS=0
SHOW_TRACKED_FILES=0
SHOW_MATCHED_FILES=0
SHOW_SUMMARY_DETAILS=0
while [[ $# -gt 0 ]]; do
case "$1" in
--check)
CHECK_MODE=1
shift
;;
--check-all)
CHECK_MODE=1
CHECK_ALL=1
shift
;;
--show-paths)
SHOW_PATHS=1
shift
;;
--show-summary-details)
SHOW_SUMMARY_DETAILS=1
shift
;;
--show-tracked-files)
SHOW_TRACKED_FILES=1
shift
;;
--show-matched-files)
SHOW_MATCHED_FILES=1
shift
;;
--verbose)
SHOW_SUMMARY_DETAILS=1
SHOW_PATHS=1
SHOW_TRACKED_FILES=1
SHOW_MATCHED_FILES=1
shift
;;
--)
shift
break
;;
-*)
echo "unknown option: $1" >&2
usage
exit 1
;;
*)
break
;;
esac
done
if [[ ${CHECK_ALL} -eq 1 ]]; then
REPORT_PATH="${1:-${DEFAULT_REPORT}}"
FAIL=0
while IFS= read -r domain; do
ARGS=(--check)
[[ ${SHOW_SUMMARY_DETAILS} -eq 1 ]] && ARGS+=(--show-summary-details)
[[ ${SHOW_PATHS} -eq 1 ]] && ARGS+=(--show-paths)
[[ ${SHOW_TRACKED_FILES} -eq 1 ]] && ARGS+=(--show-tracked-files)
[[ ${SHOW_MATCHED_FILES} -eq 1 ]] && ARGS+=(--show-matched-files)
"${BASH_SOURCE[0]}" "${ARGS[@]}" "${domain}" "${REPORT_PATH}" || FAIL=1
echo
done < <(jq -r '.domains | keys[]' "${CONFIG_PATH}")
exit "${FAIL}"
fi
DOMAIN="${1:-}"
REPORT_PATH="${2:-${DEFAULT_REPORT}}" REPORT_PATH="${2:-${DEFAULT_REPORT}}"
if [[ -z "${DOMAIN}" ]]; then
usage
exit 1
fi
if ! jq -e --arg domain "${DOMAIN}" '.domains[$domain]' "${CONFIG_PATH}" >/dev/null; then if ! jq -e --arg domain "${DOMAIN}" '.domains[$domain]' "${CONFIG_PATH}" >/dev/null; then
echo "unknown domain: ${DOMAIN}" >&2 echo "unknown domain: ${DOMAIN}" >&2
echo "available domains:" >&2 echo "available domains:" >&2
@ -43,17 +146,15 @@ fi
BASELINE="$(jq -r --arg domain "${DOMAIN}" '.domains[$domain].baseline_percent' "${CONFIG_PATH}")" BASELINE="$(jq -r --arg domain "${DOMAIN}" '.domains[$domain].baseline_percent' "${CONFIG_PATH}")"
echo "Runtime edge coverage evidence" if [[ ${SHOW_PATHS} -eq 1 ]]; then
echo "Domain: ${DOMAIN}"
echo "Baseline percent: ${BASELINE}"
echo
echo "Mapped repository paths:" echo "Mapped repository paths:"
jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}" | while IFS= read -r prefix; do jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}" | while IFS= read -r prefix; do
echo " - ${prefix}" echo " - ${prefix}"
done done
echo echo
fi
if [[ ${SHOW_TRACKED_FILES} -eq 1 ]]; then
echo "Tracked files present in repository:" echo "Tracked files present in repository:"
while IFS= read -r path; do while IFS= read -r path; do
( (
@ -62,14 +163,18 @@ while IFS= read -r path; do
) | rg "^${path//\//\\/}" || true ) | rg "^${path//\//\\/}" || true
done < <(jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}") done < <(jq -r --arg domain "${DOMAIN}" '.domains[$domain].paths[]' "${CONFIG_PATH}")
echo echo
fi
if [[ ! -f "${REPORT_PATH}" ]]; then if [[ ! -f "${REPORT_PATH}" ]]; then
echo "Coverage report not found: ${REPORT_PATH}" echo "Coverage report not found: ${REPORT_PATH}"
echo "Generate it with: make coverage-report-json" echo "Generate it with: make coverage-report-json"
if [[ ${CHECK_MODE} -eq 1 ]]; then
exit 1
fi
exit 0 exit 0
fi fi
jq -r --arg domain "${DOMAIN}" --arg root "${ROOT}" ' jq -r --arg domain "${DOMAIN}" --arg root "${ROOT}" --argjson baseline "${BASELINE}" --argjson show_matched_files "${SHOW_MATCHED_FILES}" --argjson show_summary_details "${SHOW_SUMMARY_DETAILS}" '
.domains[$domain].paths as $paths .domains[$domain].paths as $paths
| def matches_domain: | def matches_domain:
. as $file . as $file
@ -94,12 +199,44 @@ jq -r --arg domain "${DOMAIN}" --arg root "${ROOT}" '
| .regions.count += ($file.summary.regions.count // 0) | .regions.count += ($file.summary.regions.count // 0)
| .regions.covered += ($file.summary.regions.covered // 0) | .regions.covered += ($file.summary.regions.covered // 0)
) )
| "Coverage summary from " + $domain + ":", | . + {line_percent: pct(.lines.covered; .lines.count)}
" files_in_report: " + (.files | length | tostring), | (if $show_summary_details == 1 then "lines: " + (.line_percent | tostring) + "% (" + (.lines.covered|tostring) + "/" + (.lines.count|tostring) + ")" else empty end),
" lines: " + (pct(.lines.covered; .lines.count) | tostring) + "% (" + (.lines.covered|tostring) + "/" + (.lines.count|tostring) + ")", (if $show_summary_details == 1 then " files_in_report: " + (.files | length | tostring) else empty end),
" functions: " + (pct(.functions.covered; .functions.count) | tostring) + "% (" + (.functions.covered|tostring) + "/" + (.functions.count|tostring) + ")", (if $show_summary_details == 1 then
" regions: " + (pct(.regions.covered; .regions.count) | tostring) + "% (" + (.regions.covered|tostring) + "/" + (.regions.count|tostring) + ")", " functions: " + (pct(.functions.covered; .functions.count) | tostring) + "% (" + (.functions.covered|tostring) + "/" + (.functions.count|tostring) + ")"
"", else empty end),
"Matched report files:", (if $show_summary_details == 1 then
(.files[]? | " - " + .) " regions: " + (pct(.regions.covered; .regions.count) | tostring) + "% (" + (.regions.covered|tostring) + "/" + (.regions.count|tostring) + ")"
else empty end),
(if $show_matched_files == 1 then "" else empty end),
(if $show_matched_files == 1 then "Matched report files:" else empty end),
(if $show_matched_files == 1 then (.files[]? | " - " + .) else empty end)
' "${CONFIG_PATH}" "${REPORT_PATH}" ' "${CONFIG_PATH}" "${REPORT_PATH}"
LINE_PERCENT="$(line_percent "${DOMAIN}" "${REPORT_PATH}")"
if awk "BEGIN { exit !(${LINE_PERCENT} >= ${BASELINE}) }"; then
GATE_STATUS="${GREEN}PASS${RESET}"
else
GATE_STATUS="${RED}FAIL${RESET}"
fi
LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${CYAN}${LINE_PERCENT}%${RESET} >= ${RED}${BASELINE}%${RESET}]: ${GATE_STATUS}"
VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: PASS"
if awk "BEGIN { exit !(${LINE_PERCENT} >= ${BASELINE}) }"; then
VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: PASS"
else
VISIBLE_LINE="Domain: ${DOMAIN} :: GATE(lines>=baseline) [${LINE_PERCENT}% >= ${BASELINE}%]: FAIL"
fi
LINE_WIDTH="${#VISIBLE_LINE}"
BORDER="$(printf '═%.0s' $(seq 1 "${LINE_WIDTH}"))"
echo "${BORDER}"
echo "${LINE}"
echo "${BORDER}"
if [[ ${CHECK_MODE} -eq 1 ]]; then
awk "BEGIN { exit !(${LINE_PERCENT} < ${BASELINE}) }" && {
echo "Domain coverage gate failed for ${DOMAIN}: ${LINE_PERCENT}% is below baseline ${BASELINE}%"
exit 1
} || true
fi