Test Manifest

Audience: test-automation engineers and anyone wiring a suite into Stella Ops CI. Purpose: document the implemented test-manifest contract — its files, the required suite and fixture fields, the planner/validator/runner commands, and how the manifest drives CI placement.

The test manifest is the consolidation control point for suite ownership, fixture ownership, trigger routing, and CI gating. It is the source of truth that the change-impact planner and CI lanes consume; see TEST_FRAMEWORK_CONSOLIDATION_PLAN.md for the design rationale and TEST_CI_ROUTING.md for trigger placement.

Files

Required Suite Fields

Every runnable suite row must be represented or explicitly excluded.

FieldPurpose
idStable suite identifier used by CI and result artifacts.
statusactive, sample, or excluded. Sample suites are runnable examples and must not trigger product-wide suites.
ownerOwning module or test surface.
nameProject/package/suite name.
pathSource path for the suite.
runnerRunner family such as dotnet test, npm/playwright, go test, or script/tooling.
categoryPrimary category used for CI routing and .NET trait migration.
layersSecondary observed layers from inventory, such as unit, integration, e2e, contract, golden, or determinism.
ciPer-trigger policy for PR, main, nightly, weekly, manual, and overall gating state.
triggerPathsPaths that should select the suite in the change-impact planner.
runtimeCost, estimated tests/spec files, and required infrastructure.
fixtures / fixtureRootsFixture roots consumed by the suite.
seedDataSeed-data hints from project inventory.
traitsCurrent Category, Lane, and other static trait signals.
inventorySource inventory metadata and consolidation recommendation.

Required Fixture Fields

Every fixture or corpus root must be represented or explicitly excluded.

FieldPurpose
idStable fixture identifier.
ownerOwning module, doc area, or shared test surface.
pathFixture root path.
corpusTypeFixture, seed data, golden files, corpus, or generated copy classification.
fileCountInventory file count.
sizeBytesInventory byte size.
extensionsObserved file extensions.
likelyGeneratedCopyMarks generated fixture copies that should not become source-of-truth seed data.
statusactive or excluded.

Tree currency and placement

validate-test-manifest.py checks the manifest against the repository, not only against the inventory CSVs it was generated from. It fails when:

refresh-test-manifest.py places every suite that no inventory rule places:

  1. An excluded suite records its retirement note or exclusion reason.
  2. A suite that an event-triggered workflow other than the manifest lanes already runs records runs in dedicated lane: <workflow>. Such workflows supply infrastructure the sweep does not, such as Oracle or a live estate.
  3. Every other suite runs in the nightly full sweep, report-only. The sweep sends suites whose runtime.infra names Testcontainers to the apparmor-host shard and the rest to the container shards.

Reviewed infrastructure metadata

An existing curated row survives a normal inventory/manifest refresh. Correcting a known fixture dependency therefore requires a reviewed remeasurement of that row: use refresh-test-inventory.py::measure_project for source counts/traits, then refresh-test-manifest.py::build_suite_manifest for the derived fields. Retain its suite ID, path, trigger policy and other curated decisions; run the normal refresh chain afterward. Do not regenerate the entire manifest from scratch to repair one suite.

PCD-370 applies this to scanner-stellaops-scanner-triage-tests. Its supported TriagePostgresFixture requires real PostgreSQL through Testcontainers, so the reviewed infrastructure is docker-testcontainers, postgres, testcontainers. The canonical cost rule classifies that fixture suite as medium; the existing host-infrastructure shard selects it and container shards exclude it. Nightly/manual remain report-only. The manifest validator rejects this exact active project’s missing infrastructure; its self-test includes absent/partial metadata and correct host/container routing in both modes. It does not infer infrastructure for other projects from their names.

The refreshed inventory counts 56 facts and one theory (57 declarations), including the fixture migration guard and Integration traits. These counts are not execution totals: the retained PCD-361 complete project run executed 67 tests with zero failures/skips. Planner selection proves placement; it does not prove a future runner execution.

The .NET Test Sweep and .NET PR Tests workflows that the inventory credits for most suites are workflow_dispatch-only. They are not an automatic lane.

Commands

A test project was added, moved or withdrawn, or a fixture root moved:

python .gitea/scripts/test/refresh-test-inventory.py
python .gitea/scripts/test/refresh-test-manifest.py
python .gitea/scripts/test/refresh-test-triage.py
python .gitea/scripts/test/refresh-ci-gap-routing.py
python .gitea/scripts/test/refresh-fixture-manifests.py

Then validate and plan:

python .gitea/scripts/test/validate-test-manifest.py --self-test
python .gitea/scripts/test/validate-test-manifest.py \
  --out TestResults/test-manifest-validation/summary.json
python .gitea/scripts/test/validate-ci-routing.py \
  --out TestResults/test-ci-routing/summary.json
python .gitea/scripts/test/plan-test-run.py \
  --changed-file src/Scanner/StellaOps.Scanner.WebService/Program.cs \
  --out-dir TestResults/test-plan-scanner
python .gitea/scripts/test/run-test-plan.py \
  --plan TestResults/test-plan-scanner/test-plan.json \
  --out-dir TestResults/test-run-scanner
python .gitea/scripts/test/plan-test-run.py \
  --select-all \
  --runtime-cost small \
  --suite-shard-total 4 \
  --suite-shard-index 0 \
  --mode nightly \
  --out-dir TestResults/test-plan-nightly-small
python .gitea/scripts/test/summarize-test-readiness.py \
  --summary TestResults/test-run-scanner/test-run-summary.json \
  --out-dir TestResults/test-readiness-scanner \
  --require-executed \
  --report-only
python .gitea/scripts/test/render-test-dashboard.py \
  --plan TestResults/test-plan-scanner/test-plan.json \
  --run-summary TestResults/test-run-scanner/test-run-summary.json \
  --readiness TestResults/test-readiness-scanner/test-readiness-summary.json \
  --out-dir TestResults/test-dashboard-scanner

CI Placement

.gitea/workflows/test-manifest-plan.yml runs on:

The workflow deliberately does not run product-wide suites. It validates manifest drift and runs planner smoke cases for:

The uploaded artifact is test-manifest-planner under TestResults/.

.gitea/workflows/test-manifest-execution.yml is the shared execution lane:

CI-gap placement is now explicit in TEST_MANIFEST.yml: the 23 ci-gap rows are registered with targeted PR/main candidate, nightly/manual, weekly/manual, sample-only, extended, or manual-live policy according to runtime and fixture cost. These rows stay report-only until stability evidence promotes them.

See TEST_CI_EVIDENCE_DASHBOARD.md for the evidence standard. A green workflow is not all-suite execution proof unless test-run-summary.json and the dashboard matrix show executed passing rows for the relevant suites.

Current Local Evidence

As of 2026-09-27 (PCD-69, SPRINT_20260927_005):

Before this change the validator compared the manifest only with the inventory CSVs. 107 suite paths and 78 fixture roots named nothing in the tree, 85 test projects had no row, and 496 suites ran in no automatic lane. The nightly sweep logged MSB1009: Project file does not exist for the stale rows and never ran the unregistered projects, among them StellaOps.Vulnerabilities.Ingestion.Tests, which stayed red for 15 days.

As of the first implementation slice: