CI Quality Gates

Test-truth source census

The architecture lane runs tools/scripts/validate/check-test-truth.py --self-test and checks tools/scripts/validate/test-truth-baseline.json against the current AUD-2 compile graph and the prior revision. The census report records the scope and exact owner filing.

The detector finds process environment mutations without a proven serialized collection, assembly or copied runner policy, and migration inputs whose selected assembly or SQL resources cannot prove the requested fixture contract. These are source candidates; dynamic bindings and helper callers remain explicit owner obligations. The checker does not certify runtime isolation or migration execution. New/stale identities, baseline growth, changed reviewed owner metadata and closed tasks with unresolved identities fail. The self-test includes the pre-PCD-122 two-class environment conflict and migration selection controls. Full native build and repeated-run results remain separate evidence.

Audience: engineers and release owners who need to understand which automated gates can block a merge or a release, what each gate measures, and how to run or update them.

Overview

Beyond the per-module test lanes (see CI Lane Integration), Stella Ops runs a set of quality gates in CI that enforce product-level guarantees rather than individual test outcomes:

Quality gates run after the main test suite completes. The example workflow steps below were authored against build-test-deploy.yml, which has since been archived to .gitea/workflows-archived/build-test-deploy.yml; new gate wiring should plug into the live workflows. Verify the gate scripts referenced below against scripts/ci/ before relying on them.

Quality Gate Jobs

Evidence archive checks in the Python runtime job

The existing .gitea/workflows/python-runtime-compat-verify.yml job executes volume-backup.py --self-test and discovers test_backup_evidence.py on its Python 3.9 runtime, refusing zero cases or a discovered/executed count mismatch. Re-run locally with python3 devops/compose/scripts/test_backup_evidence.py -v. The offline controls exercise actual backup/verification helpers through a guarded Docker stand-in: required sources, exact member coverage and integrity, quiescence refusals, failure cleanup and preserved encryptor delegation. This is an extension of the existing job; real recovered Evidence subjects and CA-14 require their independently approved disposable proof.

Consumer census

The architecture lane runs check-consumer-census.py --self-test and the full source census from tools/scripts/validate/, comparing its exact worklist with the PR base or pre-push revision. It evaluates project and compile ownership, bounded C# hosting/extension call paths, and pinned producer or dynamic-registration witnesses. New candidates, missing owner rows, stale entries, unreadable inputs, unresolved graph boundaries and baseline growth fail the gate.

The worklist records verification and withdrawal tasks. Ambiguous overloads, helper paths, fixture roles and test-only callers remain visible; inclusion does not establish runtime absence. See the consumer census and owner rows.

Compose source binding census

The architecture lane runs the Compose source binding self-test and full census against the evaluated graph produced by its preceding AUD-2 step. Each unresolved source binding, host prerequisite and confirmed supplied-key or health finding must have an exact owner selector; infrastructure, private-input and script exceptions retain their actual source scope. Content-addressed evidence preserves all witness pairs and raw verdicts, while prior-revision comparison rejects baseline growth, missing ownership and witness stripping. An unavailable prior revision fails. No deployed consumption or runtime reachability is inferred. See the Compose census and owner report.

Reviewed client route worklist

The architecture lane runs check-client-route-closure.py --self-test and its explicit --known-boundary gate against the actual prior revision. It validates invocation identities, filed owners and source authority continuity. New/stale outcomes and unreviewed baseline changes fail. Full mode returns UNVERIFIED while owner-filed URI/type/composition or capture questions remain. AUD-1 is DONE as a census row; each remaining unknown is an owner task enforced by the owner-worklist gate below. See the source census. The review, authority and introduction manifests pin the exact content of every reviewed input. An edit to one, even a pure line shift, makes both gates return UNVERIFIED (exit 2), name the stale input and name the rebind command. To rebind, merge main and commit the change, then run python tools/scripts/validate/rebind-client-route-review-chain.py (a dry run that prints the plan) and repeat it with --write. The command re-anchors a review only when its TypeScript caller merely moved it: the census call keeps its identity, context, expression, receiver and receiver type, and the read-only TypeScript checker binds every moved framework receiver to the reviewed type and declaration. The installed dependency versions must equal package-lock.json; without node_modules the command returns UNVERIFIED. It then moves the chain ref to the commit and recomputes the pinned hashes. --write runs the closure gate and restores every file if the gate fails. It refuses new or stale identities, changed receivers or proofs, deleted inputs, changed C#, Compose, capture, lock or configuration inputs, changed introduction anchors and changed route-authority sources or trees. Each of those needs a person’s review, and the refusal names the files changed since the chain ref. After reading those diffs, a reviewer accepts an input that has no reviewed calls of its own with --reviewed-input <path> --review-note "<what was checked>": an introduction context input (the introduction structure validator must still pass on the new content), a configuration or declaration input, or a route-authority source or tree (the note is appended to its reason). Every pin site of that input moves together. A changed caller, the dependency lock and a deleted input stay refused; they need a reviewed edit of the review entries. --write also runs the closure --self-test. The architecture step runs the command’s own --self-test.

An outcome’s identity includes its call text, so a change to only the arguments of an owner-filed UNVERIFIED call reads as one NEW and one STALE outcome. After a person confirms that only the arguments changed, the baseline entry keeps its filed owner task through a context_carry record: previous_context_sha256, filed_context_sha256 and a reason. The closure gate accepts it only for an UNVERIFIED outcome that keeps the path, invocation, method, route, kind, URI expression and every review field of the entry it replaces. That entry must leave the baseline in the same change, and the filed context must chain back to the original filing. The owner-worklist gate resolves the carried outcome to its filed identity and requires the call to keep the filed URI expression, receiver and receiver type. Any other change remains a NEW outcome.

CCP-083 adds one individually reviewed source introduction for the private AttestorLinkClient.PostAsync transport. Its three direct callers supply ordinary literal path inputs to the existing URI builder. The fixed proof checks every helper occurrence, the sealed class/private declarations, request/header flow and the configured URI branches; repinned negative controls exercise structural refusal. It remains an exception with dynamic-unverified kind. A runtime BackendUrl can preserve a path prefix, and BaseAddress uses relative URI resolution, so this proof provides no final route, destination or capture match. Its exact new identity is admitted through the pinned introduction chain; the baseline has no general growth allowance. Reflection, dynamic reachability and unsupported syntax are not treated as compiler-proven closure. Changes to this fixed shape require review.

PCD-871 adds one fixed OfflineKit Policy-reader introduction. Its private GET helper has exactly two reviewed path inputs: exception listing and escaped exception history. The lexical contract pins request construction, tenant-token use, deadline and response bounds; ten repinned mutations independently reject changed methods, tenants, paths, escaping, authentication, limits, visibility and helper escape. Its raw kind remains dynamic-unverified, with configured destination and final route unresolved. The source review admits one exact call, not a new capture match or general baseline growth. Native client/host controls and the real HTTP simulation are separate from deployed acceptance.

CCP-082 adds exactly three fixed operation-transport introductions: the configured OIDC redemption request, the Console authentication start, and its two-destination owner request lease. The OIDC call retains the extractor’s receiver-unverified kind and UNKNOWN receiver observation; its separate source proof binds the real factory, request and no-redirect registration. The two Console calls retain dynamic-unverified. All three remain source-only exceptions with null final routes and unknown configured destinations, never capture or live-serving proof. Their three exact baseline rows use the existing fixed-introduction gate; no generic growth rule is added. The closed token contracts, actual TypeScript framework observations, Git absence evidence and repinned semantic controls refuse changed request flow, hidden/duplicate calls, altered targets or provenance. The Web class remainder and OIDC constructor/factory inventory also reject constructor or class-initializer replacements of the reviewed receiver/configuration. This is a conservative proof of these reviewed shapes, not general C#/TypeScript interprocedural analysis. The prior finite-branch and provenance-helper proofs remain separate and unchanged.

A NEW HTTP call whose callee publishes no owner capture, or whose capture omits the route, can be admitted after a review by an agent or a person (owner ruling 2026-10-10). The reviewer writes an admission request with three parts:

The validator proves the served route from live C# code of the owning service (AUD-11, hardened by AUD-12, 2026-10-10, and AUD-13, 2026-10-19). Its threat model is accidental drift by honest authors: a mapping that is commented out, disabled, moved to another group or file, re-prefixed or given another method. It is a conservative syntactic proof, not a C# compiler. It accepts only the shapes described here, subject to the explicit residual limits below. It does not prove that a deliberately obfuscated source maps the route. Each mapping, segment and link statement must:

The segments and links must form the mapping receiver’s actual group chain. The receiver’s binding in the block of its use must be a reviewed segment var <group> = <parent>.MapGroup("<literal>"); or a reviewed link var <group> = <parent>;. A receiver that is a parameter of the method that holds the mapping needs two reviewed links: the method header, and the call that passes the parent group. That call must be the extension call on the group (lookup.MapFacts()), a call qualified by the header’s declaring type (FactsEndpoints.MapFacts(lookup)), or an unqualified call where the header is in scope (inside its type, or inside the enclosing block of a local function). The method name must be declared once in the header’s source and nowhere else in the owner’s tree, so that overload resolution cannot choose another method. The chain ends at a top-level var app = builder.Build(); whose builder comes from WebApplication.CreateBuilder(...) (or CreateSlimBuilder or CreateEmptyBuilder). A route part is a plain string literal or a reviewed const string declaration of the same source. That declaration must be in the type or block of its use, and the source must declare the name only once; C# name lookup then binds the use to it. A var (...) deconstruction declaration that shadows the route constant is also refused. Every reviewed statement must be on the chain. A prefix that comes from configuration, such as an options property default, is not provable from source and is refused.

Each variable on the chain (the receiver, every group, alias and parameter, the application and its builder) must be written only by its declaration. Every other occurrence of the name in its scope must be a receiver (lookup.MapGet(...)), one whole by-value call argument, or return <name>;. The validator refuses a deconstruction, a tuple swap, an out or ref argument, the name inside an interpolated string, and an assignment anywhere in the scope, also after the mapping and inside a local function or lambda that is declared later. On a route group, a member call must be an endpoint mapping (Map...) or an allowed endpoint convention. A by-value argument of a group may only go to a Map... method or to ArgumentNullException.ThrowIfNull.

Only these endpoint conventions may follow Map<Verb>(...) or MapGroup(...), or be called on a chain group: RequireAuthorization, AllowAnonymous, WithTags, WithName, WithGroupName, WithOpenApi, WithSummary, WithDescription, WithDisplayName, AddEndpointFilter, AddEndpointFilterFactory, DisableAntiforgery, RequireCors, RequireRateLimiting, DisableRateLimiting, ExcludeFromDescription, CacheOutput, ShortCircuit, DisableHttpMetrics, WithRequestTimeout, DisableRequestTimeout, Produces, ProducesProblem, ProducesValidationProblem and Accepts. Their framework implementations preserve the endpoint’s method, route and host match; the proof does not resolve overloads to framework symbols. The validator refuses WithMetadata (it can add an HttpMethodMetadata), Add, Finally, WithOrder, RequireHost and every other call. A mapping must be Map<Verb>; MapMethods is not admitted.

The validator neither follows nor refuses the following shapes (residual limits):

Then run python tools/scripts/validate/rebind-client-route-review-chain.py --admit <request.json> (a dry run), and repeat it with --write --base-ref <main tip>. The command finds exactly that census call. It refuses:

It writes one served-route-admission review with disposition capture, and pins the caller and every served source at the chain ref. The baseline does not grow, and the owner-worklist gate does not change. The closure gate validates the admission again on every run. The plain rebind handles later edits: it re-anchors an admitted caller that only moved, and it repins a changed served-route source only while every admission still validates on it. A changed mapping, route or receiver needs a new review. Re-verification refuses an edit that comments out, disables or moves a reviewed statement in one of the shapes described above, so the rebind does not repin it. That holds only within the threat model and the residual limits. AUD-11 stated it without limits, and its verifier found three edits of the real admission that re-verification passed: the mapping moved into a raw string inside a raw interpolated hole, and the group reassigned by a deconstruction or by a local function declared after the mapping. AUD-12 refuses all three. When the owner later publishes a capture that contains the route, the call becomes a capture match. The closure gate then reports the admission as stale, and the landing that adds the capture removes the entry.

Reviewed documentation currency

The architecture lane runs check-doc-currency.py --self-test and the default full census from tools/scripts/validate/, comparing the exact baseline against the PR base or pre-push revision. New identities, changed contexts, stale entries and unrelated baseline growth fail. Unclassified API dispositions or changed source/member proof return UNVERIFIED. All 168 static hints currently have source-reviewed dispositions; publication remains distinct from implemented capability and live availability. See the current source integration and owners.

Reviewed documentation examples

The architecture lane runs check-api-documentation-examples.py --self-test and the full check from tools/scripts/validate/, comparing against the PR base or pre-push revision. The check extracts curl, HTTP request, table and JSON method/URL examples from living Markdown. It matches each method/path identity against the AUD-1 route authority, the tracked YAML contracts and the reviewed source aliases. Every non-match must be an exact entry in api-documentation-examples-baseline.json: either a finding bound to an owner row, or a reviewed dated-history exception. The same step verifies that cli-documentation-owner-worklist.json covers every entry of the CLI unshipped-example baseline at its current occurrences. Entries pin document witnesses and occurrence lines, so an edit to a document that holds a baselined example fails the step until the ledger is rebound. To rebind, run python tools/scripts/validate/rebind-documentation-example-ledgers.py (a dry run that prints the plan), then repeat it with --write --owner <lane>. It refreshes witnesses and moved occurrences, drops examples that left the tree and regenerates the affected owner-row markers. It refuses new examples, changed owners or groups, CLI scope moves, a CLI baseline that disagrees with the extraction, an exception document nobody re-read (--reviewed-exception-document) and markers in another lane’s DOING row; those need a review decision. The same step runs its --self-test. The ledger is shrink-only, so a reworded line that carries a baselined example cannot be re-filed under its new text: restore the line, or, when a filed row already owns the defect, state the fact without a method-and-path pair. When the step instead reports a stale AUD-1 route authority, its message names the AUD-1 rebind command described above. An example of an AUD-1 served-route admission matches the admitted route and needs no ledger entry. The route is proven again with the same live-code validator, so an admission whose mapping was disabled or moved, in a shape that the validator follows, admits no example (see the residual limits of the AUD-1 admission above). An example of any other route that no capture serves stays NEW. Endpoint identity does not prove request bodies, authorization or live availability. See the census report.

The lane also runs check-sidecar-digests.py. It verifies every tracked *.sha256 and SHA256SUMS entry outside frozen zones against its Git index blob. Its only baselined entries are two canonical-JSON reference specimens, and the gate recomputes their recipe.

Push-lane liveness detector

The architecture lane runs ci-push-lane-history.py --self-test and --inventory from tools/scripts/validate/. These controls cover push-trigger enumeration, main-branch identity, completed versus cancelled/skipped runs, and bounded history. The live read-only probe remains check-ci-gate-liveness.sh --history-only; it reports every push lane’s last completion and returns UNVERIFIED when history is unavailable. See the census method.

Estate image staleness

tools/scripts/validate/check-estate-image-staleness.sh reads running containers’ /app/buildinfo.json and committed source history. It reports dirty/unverified stamps and missed migration/persistence-registration changes. General image age is outside this check. CredentialStore adopters additionally watch their composition roots and shared persistence/ re-seal projects; the roster is checked against both build catalogs’ actual project closures.

Run "C:/Program Files/Git/bin/bash.exe" tools/scripts/validate/check-estate-image-staleness.sh --self-test on Windows (or bash ... --self-test on a native Bash host). The local controls use synthetic Git commits and a Docker stub: stale/dirty stamps and shared/Program changes for every current adopter fail; current/unrelated commits and non-adopter boundaries pass. An independent Integrations control detects a missing current roster entry. Source verified against aada41fd38 (2026-10-04, PCD-366). The self-test accesses no live container and performs no deployment. A normal invocation is a read-only live probe; its findings require separately authorized image work, and its explicit skip result provides no estate verification.

Curated OpenAPI aggregate currency

The existing architecture workflow runs node src/Api/StellaOps.Api.OpenApi/compose.mjs --self-test and --check after installing its locked Node dependencies. The check compares stella.yaml with a fresh composition of the curated owner inputs and shared components without rewriting it. Its controls reject a seeded retired scope, changed owner paths, missing output, incorrect draft status, missing local reference targets and empty inputs. Owner-local parameters and responses are included with matching namespaced references. This is static contract currency, not live gateway coverage. Regenerate with npm run api:compose; retain the draft markers and historical baseline.

.NET workspace distribution guard

tools/scripts/validate/check-dotnet-workspace-distribution.py rejects the removed Visual Studio setup-interop filename, dependency identity and embedded binary markers. It reads actual publish directories and nested ZIP/TAR/gzip/XZ/bzip2 members without extracting them; malformed, unsupported compressed, empty or oversized inputs fail verification. Run python3 tools/scripts/validate/check-dotnet-workspace-distribution.py --self-test before checking a payload. --report <new-file.json> records file hashes and package declarations. This is component-absence evidence, not clearance of every other licence. The package-provenance auditor also maps nested native binaries through the exact containing NuGet ZIP bytes, including extensionless framework binaries.

The service-image builder checks its final publish context and rebuilt Scanner bundles. The CLI publication workflow checks the final image’s copied /app payload and all exported image layers before push. Use --docker-save <archive.tar> on a single-image export: it verifies layer blob/diff hashes and inspects earlier bytes even when a later layer overwrites or deletes their path. Direct/manual publication must run the same guard on the final payload. The reviewed notice catalogue must also accompany the image; an absence check does not prove notice delivery or the consumer’s M1 behaviour.

For a versioned service build, set PUBLISH_SERVICE_VERSION=1.0.0 alongside the chosen IMAGE_TAG. This sets assembly/buildinfo/OCI version metadata and propagates the same MSBuild value to Scanner language, OS and secrets bundle publishes. A release tag alone does not override assembly metadata. The CLI Dockerfile uses its VERSION build argument for both the OCI label and StellaOpsServiceVersion; the manual CI workflow derives a bare semantic version from a versioned tag.

P13 retention declaration inventory

The existing .gitea/workflows/test-architecture.yml pack runs P13RetentionClassConformanceTests, including the self-tested tools/scripts/validate/check-p13-retention.py detector. Its PR trigger covers source C#/SQL, the detector, inventory, ADR/review and owning sprint; pushes to main remain unfiltered. The normal inventory/growth result accounts for source declarations. --strict additionally refuses pending repairs; neither result proves executed retention or live database compliance. See P13 inventory and limits for the exact commands.

Request-supplied tenant binding

The same pack runs RequestTenantBindingConformanceTests. It fails when an HTTP endpoint takes a tenant from the request and never compares it to the authenticated tenant. The detector (Helpers/RequestTenantBindingDetector.cs) scans production C# under src/ (tests, build output and src/__Obsoleted/ excluded) for three shapes:

An instance passes when the value is compared to the principal: an equality or membership test between the request value and IStellaOpsTenantAccessor, StellaOpsTenantResolver, HttpContext.GetTenant/RequireTenant/TryGetTenant, the stellaops:tenant claim, a service RequestContext, or a helper that reads one. The detector follows the value through local aliases and up to three calls into helpers. A comparison that runs only when the principal value is present (!string.IsNullOrWhiteSpace(claim) && ...) is fail-open and does not count. These forms also pass: a parameter the handler never reads; a route value that an endpoint filter on the route group compares; a body member that neither the handler nor anything it passes the record to reads; a member overwritten with request with { TenantId = ... }.

Each other instance must be listed in RequestTenantBindingBaseline.json beside the test, either under open (an open fix row, owner: PCD-n) or under reviewed (NOT-AN-ISSUE or NEEDS-RUNTIME-CHECK, with the audit ID and the reason). The baseline is shrink-only: an unlisted instance fails, and an entry whose instance is fixed or moved fails until it is deleted. Counts are exact. Self-test facts run the detector on fixture sources with each shape, each compliant form and a fail-open comparison, and prove that the baseline diff reports a new instance, a stale entry and a wrong count. Run it alone with pwsh ./tools/scripts/test-targeted-xunit.ps1 -Project src/__Tests/architecture/StellaOps.Architecture.Contracts.Tests/StellaOps.Architecture.Contracts.Tests.csproj -Class "*RequestTenantBindingConformanceTests".

The scan is text-level. It does not see a tenant under another name, a value read through a dictionary or helper it cannot name, a comparison whose mismatch does not reject, or a comparison on one branch while another branch uses the value. Use it with the rule that handlers take the tenant from IStellaOpsTenantAccessor, not as the only control. The defect class and the reviewed verdicts come from the PCD-849 audit.

Reachability Quality Gate

Script: .gitea/scripts/metrics/compute-reachability-metrics.sh
Config: .gitea/scripts/metrics/reachability-thresholds.yaml

Validates that the scanner meets recall/precision thresholds against the ground-truth corpus.

Metrics Computed

MetricDescriptionThreshold
runtime_dependency_recall% of runtime dep vulns detected≥ 95%
unreachable_false_positivesFP rate for unreachable findings≤ 5%
reachability_underreportUnderreporting rate≤ 10%
os_package_recall% of OS package vulns detected≥ 92%
code_vuln_recall% of code vulns detected≥ 88%
config_vuln_recall% of config vulns detected≥ 85%

Running Locally

# Dry run (no enforcement)
./.gitea/scripts/metrics/compute-reachability-metrics.sh --dry-run

# Full run against corpus
./.gitea/scripts/metrics/compute-reachability-metrics.sh

TTFS Regression Gate

Script: .gitea/scripts/metrics/compute-ttfs-metrics.sh
Baseline: src/__Tests/__Benchmarks/baselines/ttfs-baseline.json

Detects performance regressions in Time-to-First-Signal.

Metrics Computed

MetricDescriptionThreshold
ttfs_p50_msP50 time to first signal≤ baseline + 10%
ttfs_p95_msP95 time to first signal≤ baseline + 15%
ttfs_max_msMaximum TTFS≤ baseline + 25%

Baseline Format

{
  "ttfs_p50_ms": 450,
  "ttfs_p95_ms": 1200,
  "ttfs_max_ms": 3000,
  "measured_at": "2025-12-16T00:00:00Z",
  "sample_count": 1000
}

Performance SLO Gate

Script: .gitea/scripts/metrics/enforce-performance-slos.sh
Config: .gitea/scripts/metrics/performance-slos.yaml

Enforces scan time and compute budget SLOs.

SLOs Enforced

SLODescriptionTarget
scan_time_p50_msP50 scan time≤ 120,000ms (2 min)
scan_time_p95_msP95 scan time≤ 300,000ms (5 min)
memory_peak_mbPeak memory usage≤ 2048 MB
cpu_secondsTotal CPU time≤ 120 seconds

Workflow Integration

Quality gates are integrated into the main CI workflow:

# Illustrative quality-gates job (modelled on the archived build-test-deploy.yml)

quality-gates:
  runs-on: ${{ vars.LINUX_RUNNER_LABEL || 'ubuntu-latest' }}
  needs: build-test
  steps:
    - name: Reachability quality gate
      run: ./.gitea/scripts/metrics/compute-reachability-metrics.sh

    - name: TTFS regression gate
      run: ./.gitea/scripts/metrics/compute-ttfs-metrics.sh

    - name: Performance SLO gate
      run: ./.gitea/scripts/metrics/enforce-performance-slos.sh --warn-only

Failure Modes

Hard Failure (Blocks Merge)

Soft Failure (Warning Only)

Adding New Quality Gates

  1. Create computation script under .gitea/scripts/metrics/ (alongside the existing gate scripts)
  2. Add threshold configuration (YAML or JSON)
  3. Integrate into workflow as a new step
  4. Update this documentation
  5. Add to sprint tracking

Troubleshooting

Gate Fails on PR but Passes on Main

Check for:

Baseline Drift

If baselines become stale:

# Regenerate baselines
./.gitea/scripts/metrics/compute-ttfs-metrics.sh --update-baseline
./.gitea/scripts/metrics/compute-reachability-metrics.sh --update-baseline

Turn #6 Quality Gates (2026-01-27)

Source: Testing Enhancements (Automation Turn #6) Sprint: docs/implplan/SPRINT_0127_002_DOCS_testing_enhancements_turn6.md

Verify before wiring. The gate scripts named in this section (check-intent-violations.sh, check-observability-contracts.sh, check-evidence-chain.sh, run-longevity-gate.sh, run-interop-gate.sh, check-post-incident-tests.sh) describe the intended design from the Turn #6 sprint and may not yet be committed. Confirm each script exists under .gitea/scripts/ before relying on it in a workflow.

Intent Violation Gate

Purpose: Detect test changes that violate declared intent categories.

Script: scripts/ci/check-intent-violations.sh

CheckDescriptionAction
Intent missingNon-trivial test without Intent traitWarning (regulatory modules: Error)
Intent contradictionTest behavior contradicts declared intentError
Intent coverage dropModule intent coverage decreasedWarning

Enforcement:

Observability Contract Gate

Purpose: Validate OTel spans, structured logs, and metrics contracts.

Script: scripts/ci/check-observability-contracts.sh

CheckDescriptionThreshold
Required spans missingCore operation spans not emittedError
Span attribute missingRequired attributes not presentError
High cardinality attributeLabel cardinality exceeds limitWarning (> 50), Error (> 100)
PII in logsSensitive data patterns in log outputError
Missing log fieldsRequired fields not presentWarning

Enforcement:

Evidence Chain Gate

Purpose: Verify requirement -> test -> artifact traceability.

Script: scripts/ci/check-evidence-chain.sh

CheckDescriptionAction
Orphaned testRegulatory test without Requirement attributeWarning
Artifact hash driftArtifact hash changed unexpectedlyError
Artifact non-deterministicMultiple runs produce different artifactsError
Traceability gapRequirement without test coverageWarning

Enforcement:

Longevity Gate (Release Gating)

Purpose: Detect memory leaks, connection leaks, and counter drift under sustained load.

Script: scripts/ci/run-longevity-gate.sh Cadence: Nightly + pre-release

MetricDescriptionThreshold
Memory growth rateMemory increase per hour≤ 1%
Connection pool leaksUnreturned connections0
Counter driftCounter value outside expected rangeError
GC pressureGen2 collections per hour≤ 10

Enforcement:

Interop Gate (Release Gating)

Purpose: Validate cross-version and environment compatibility.

Script: scripts/ci/run-interop-gate.sh Cadence: Weekly + pre-release

CheckDescriptionThreshold
N-1 compatibilityCurrent server with previous clientMust pass
N+1 compatibilityPrevious server with current clientMust pass
Environment equivalenceSame results across infra profiles≤ 5% deviation

Profiles Tested:

Enforcement:

Post-Incident Gate

Purpose: Ensure incident-derived tests are maintained and passing.

Script: scripts/ci/check-post-incident-tests.sh

CheckDescriptionAction
Incident test failingPostIncident test not passingError (P1/P2), Warning (P3)
Incident test missing metadataMissing IncidentId or RootCauseWarning
Incident coverageP1/P2 incidents without testsError

Enforcement:


Gate Summary by Gating Level

PR-Gating (Must Pass for Merge)

GateScope
Reachability QualityAll
TTFS RegressionAll
Intent ViolationRegulatory modules
Observability ContractW1 modules
Evidence ChainRegulatory modules
Post-Incident (P1/P2)All

Release-Gating (Must Pass for Release)

GateScope
All PR gatesAll
LongevityWorker modules
InteropSchema/API-dependent modules
Post-Incident (all)All
Performance SLOAll

Warning-Only (Informational)

GateScope
Intent missingNon-regulatory modules
Intent coverage dropAll
Orphaned testAll
Traceability gapAll

Exact client owner worklist

The existing architecture lane runs check-client-route-owner-worklist.py --self-test and --filed. --filed verifies the filing manifest against its pinned filing baseline. It then checks current state against the current baseline and an actual full client census. Every open UNVERIFIED outcome must keep its original review metadata and a real owner task. Every raw unknown must be owner-filed or a filed finding. No task may close or be archived while one of its identities stays open, unless an independently validated resolution covers it. Settled identities release their row; the full child returns UNVERIFIED exit 2 while unknowns remain. A passing gate proves ownership integrity, not served-route closure. Filing receipt.