Skip to content

Reuse digest-bound QA evidence without weakening coverage #113

Description

@alexeygrigorev

Outcome

Keep the verification quality established by #104 while avoiding work that is provably redundant. Every changed path is resolved through a versioned ownership graph, every required verification component is either rerun or satisfied by independently validated evidence for the exact same relevant inputs, and every ambiguity fails closed to the existing full-regression profile.

This issue changes verification orchestration and evidence handling. It does not reduce the independent tester or product-manager gates, and it does not make a passing result portable merely because two revisions have similar commit messages, branch names, or file lists.

Authority and baseline

The implementation starts from the accepted #104 behavior. Until a valid evidence envelope exists for a component, that component runs. Legacy logs, screenshots, comments, caches, and artifacts without the schema introduced here are not reusable evidence.

Definitions

  • Candidate: the canonical base/head comparison selected with the same push and workflow_dispatch rules as Run change-selective CI with four-hour full regression #104.
  • Verification component: one independently reportable check or suite, such as quality checks, a Django test-label closure, core browser tests, full browser tests, compatibility artifacts, or the production-container contract.
  • Relevant inputs: the transitive, graph-declared source, tests, fixtures, configuration, toolchain, and environment that can affect one component's result.
  • Full regression: a fresh run of the existing Run change-selective CI with four-hour full regression #104 full profile: locked-environment/quality and CI-policy checks, fresh-SQLite migrations plus make test, core Playwright, and production-image/static/liveness checks. It does not mean that previously unimplemented Build deterministic SQLite test factories and a parallel-safe Django/Playwright harness #75/Implement release-critical contract, security, accessibility, browser, and fault suites #76 suites silently become available.
  • Full browser: make test-playwright, in addition to the core browser portion of full regression.
  • Render impact: a change to templates, CSS, browser JavaScript, rendered data shape, shared render context, URLs/redirects/navigation, or the browser/screenshot harness and fixtures.
  • Reuse: satisfying a required component with a still-valid success envelope whose relevant-input and environment digests match exactly. Cache hits and skipped jobs are not evidence reuse.
  • Skipped: an applicable component deliberately not executed, most notably an unchanged scheduled state. A skipped component never satisfies a candidate's required coverage.
  • Not applicable: the graph proves that a component is outside the candidate's impact closure.

Versioned impact-ownership graph

Add one code-owned, schema-validated graph under ci/ and make it the single source used by local Make targets, push CI, scheduled CI, and handoff validation. Each node declares:

  • stable node ID and owning app/surface;
  • NUL-safe repository path matchers, including how old and new paths of rename/copy changes are handled;
  • direct verification components and their Make target or command ID;
  • downstream node IDs whose behavior can be affected;
  • relevant test, fixture, generated-artifact, tool, and configuration input sets;
  • risk flags: shared_runtime, auth_security_privacy, schema_migration, dependency_toolchain, global_fixture, test_infrastructure, deployment_runtime, compatibility_contract, and render_impact;
  • evidence validity class and required environment dimensions.

The graph must reject duplicate node IDs, dangling edges, cycles that are not explicitly represented as a reviewed strongly connected group, ambiguous path ownership, an empty verification closure, and unrecognized schema fields. A graph/schema/policy change itself forces fresh full regression. Path parsing must preserve spaces, Unicode, leading dashes, tabs, newlines, deletions, renames, and copies without shell evaluation.

The initial app-root closures must retain the exact reviewed #104 minimums:

Changed root Fresh Django labels in the focused profile
api api
cadmin cadmin
content accounts content.tests core
courses accounts api cadmin content.tests core courses data
data api cadmin courses data
jobs jobs
management_api api management_api
management_auth api core management_api management_auth
review_import accounts review_import
studio accounts core studio

core, accounts, and every currently testless/shared root (content_sync, email_app, and events) remain full-regression triggers until a later groomed issue supplies reviewed ownership and downstream coverage. A candidate spanning more than one independently owned app root, an unowned path, an unknown graph node/edge, or a graph evaluation error also forces full regression. Adding a package, command, fixture root, or verification suite without adding graph ownership must therefore make CI broader, never narrower.

The emitted plan is deterministic JSON. It records normalized changed paths, direct nodes, downstream closure, risk reasons, each required component, render impact, full/focused decision, and graph/policy digests. Reversing input order or rerunning on the same base/head and environment must produce a byte-identical plan after volatile timestamps are excluded.

Exact trigger matrix

The classifier applies the first matching conservative rule. “Fresh” means prior evidence cannot replace that automated suite for this candidate. Visual evidence remains governed separately by the render-impact rule.

Change/condition Django + quality Browser Container/specialized checks Screenshot evidence
Unknown/unowned/ambiguous path or graph error; multiple app roots Fresh full regression Fresh core browser Fresh production-container contract Fresh only if render impact is also present; otherwise exact-digest reuse or N/A
core, accounts, auth/session/permission/security/privacy behavior, shared middleware/settings/runtime Fresh full regression Fresh core browser; full browser when the graph marks browser/auth/navigation impact Fresh production-container contract Same render rule
Any migration, global schema/migration harness, global factory/fixture, database backend/test settings Fresh full regression Fresh core browser Fresh production-container contract Same render rule
pyproject.toml, uv.lock, Python/runtime pins, dependency install/build inputs Fresh full regression Fresh core browser; full browser when browser dependencies changed Fresh production-container contract Same render rule
ci/**, verification graph/policy/schema, relevant .github/**, Makefile, test discovery/configuration, conftest*, browser harness, or add/delete/rename/skip-marker changes to tests Fresh full regression Fresh full browser when browser infrastructure/tests are affected; otherwise fresh core Fresh production-container contract Fresh when screenshot harness/fixture/render inputs changed; otherwise exact-digest reuse or N/A
Templates, CSS, browser JS, base render context, view/serializer/query output shape consumed by templates, URLconf, redirects, menu/breadcrumb/canonical navigation, fonts/icons/layout media Fresh full regression Fresh full browser Fresh production-container contract Fresh independent desktop and mobile captures for every affected critical route/state
Deployment/container/runtime configuration, entrypoint, static collection, health/liveness, deploy workflow Fresh full regression Fresh core browser Fresh production-container contract and the affected deployment checks Same render rule; live/deployed captures cannot cross a release/environment revision
Compatibility pinned-source/build inputs, URL/SEO/feed/sitemap/robots/redirect contracts, or generated public artifacts Fresh full regression Fresh full browser when routes or rendered output are affected; otherwise fresh core Fresh affected compatibility/source-artifact checks Same render rule
One owned app root with no force-full risk Fresh #104 Django downstream closure plus quality components whose relevant source inputs changed Rerun affected browser components; valid evidence may satisfy unchanged components Rerun affected components; valid evidence may satisfy unchanged components Same render rule
Prose-only non-policy documentation with no executable/generated-public input Fresh classifier/evidence validation Valid evidence reuse or N/A Valid evidence reuse or N/A Valid reuse or N/A

The existing #104 source-range ambiguity, shallow-history, event, and fallback behavior remains authoritative. Failure to calculate a complete base/head range is a full-regression reason. Neither a label nor a manually supplied path list may override a full trigger.

Digest-bound evidence envelope

Introduce a versioned JSON envelope and JSON Schema. One envelope represents one component result and contains at least:

  • schema/policy/ownership-graph versions and SHA-256 digests;
  • repository identity, canonical base/head commit IDs, Git object algorithm and head tree OID;
  • a canonical SHA-256 source-manifest digest over sorted NUL-safe tuples of repository-relative path, mode, and blob/submodule OID;
  • component ID, exact command/Make target, selection reason, direct nodes, downstream closure, and risk flags;
  • a closed relevant-input manifest and aggregate SHA-256 digests for production source, test source, fixtures/golden files, generated inputs, tool/dependency inputs, and configuration inputs;
  • environment fingerprint: OS/image identity, architecture, Python/Django/uv versions, database/backend, browser/Playwright versions where applicable, and an allowlisted non-secret configuration profile. Never store secret values or hashes of secret values; use an opaque CI environment revision when behavior depends on protected configuration;
  • origin/provenance: producer role, repository, workflow path/ref, run/job/attempt and artifact IDs for CI, or tester identity plus issue/worktree identity for local human evidence;
  • start/completion times, normalized result, exit status, test/assertion counts, and machine-readable output/artifact content digests;
  • produced_at, expires_at, validity class, and any superseding result;
  • for screenshots: route/state, viewport, browser, render-input digest, image SHA-256, capture time, and independent tester inspection verdict.

Canonicalization and digest algorithms are part of the schema and have golden-vector tests. A digest comparison must compare type, algorithm, and bytes; no prefix or string-coercion matches are allowed.

Validity classes are explicit:

Evidence class Maximum reuse age
Standard deterministic automated checks 7 days
Dependency/security/container/runtime checks 24 hours
Tester-inspected local screenshots 7 days and only within the same open issue/candidate lineage
Live/deployed checks 4 hours and only for the exact release SHA plus environment revision

The scheduler's unchanged-state decision is not a reuse claim and is therefore allowed to report skipped: unchanged_state even after prior evidence expires. Expired evidence cannot satisfy a later candidate or release gate.

Evidence is invalid, and the component must rerun, when any relevant source/test/fixture/tool/config/environment digest differs; the source manifest or artifact is missing/corrupt; the graph or policy changed; the envelope is unknown/malformed/expired; the origin is not trusted for that consumer; the result is anything other than completed success; or a later same-scope result for the same state failed, timed out, was cancelled, became stale, or required action. A failed/cancelled/stale result must never fall back to an older passing envelope. A result for a strict superset may be reused only when the component ID, environment, and its declared relevant-input digest are identical—not by name similarity.

CI may reuse only provenance-validated GitHub Actions artifacts from this repository and an allowlisted workflow/job on the protected development lineage. Local engineer output is useful input to the tester but cannot satisfy CI or approve itself. Cache keys, console text, issue comments, and job conclusions without the envelope and matching artifacts are not evidence.

Screenshot and large-content contract

The classifier emits a separate render fingerprint. It includes all affected template/layout/static/browser-code inputs, template-consumed data-shape contracts, shared context, routes/redirects/navigation, browser and screenshot harnesses, screenshot fixtures, viewport, and browser version.

  • If the render fingerprint changes, affected critical flows require new desktop and mobile Playwright screenshots and independent inspection.
  • If it does not change, a still-valid tester-inspected screenshot envelope with an identical render fingerprint may be reused. Reuse must name the prior evidence ID, image digest, age, route/state, and reviewer.
  • A missing, expired, uninspected, or hash-mismatched image cannot be reused and causes a fresh capture.
  • Value-only content/catalog changes with an unchanged validated render/data-shape contract use exhaustive deterministic artifacts and invariants (counts, identities, ordering, uniqueness, URL/metadata completeness, and stable artifact digests) rather than manually reopening every page. Such evidence does not excuse a fresh screenshot when the render fingerprint changed.
  • A screenshot file's existence or a Playwright pass without independent visual inspection is insufficient evidence.

Independent selection verification and role separation

The engineer may generate a candidate plan and propose reusable evidence. The independent tester must recompute the plan from canonical base/head, inspect every reuse envelope and provenance record, validate the relevant-input/environment/artifact digests, and confirm the four reporting buckets. The tester may always widen coverage. Any disagreement, unverifiable evidence, or classifier mismatch forces fresh full regression; it is not resolved by the engineer self-approving the plan.

The product manager accepts only after the tester records a PASS and the report shows all required components satisfied by a current rerun or valid reuse. The existing #104 CI/deployment gate remains fail-closed. A required component reported as skipped, absent, or not applicable without graph proof blocks the gate.

Scheduled full regression

Keep the dedicated schedule at 17 */4 * * *, with read-only contents/actions permissions and no deployment/cloud authority.

On every tick, compute a verification_state_sha256 from the complete tracked source tree plus graph, policy, test/tool/dependency, and environment fingerprints. Locate the most recent provenance-valid successful scheduled full-regression envelope on the development lineage:

  • identical state and no later failed/cancelled/stale/timed-out run for that state: do not run suites; report skipped: unchanged_state with both state digests and the anchor run;
  • different state: run fresh full regression and publish new component envelopes plus the aggregate state envelope;
  • no usable anchor, malformed/missing artifact, history/API failure, unknown comparison, or a later non-successful run: assume changed and run fresh full regression.

Commit ID alone is not the state proof: metadata-only commits with an identical tree/tool/environment state may skip, while a changed graph, lockfile, test/tool image, or environment revision must run even if application blobs are unchanged. Search is bounded and tested, as in #104. The scheduled gate succeeds only for a complete fresh full run or a validated unchanged-state skip; partial runs never become anchors.

Required reporting

Produce a machine-readable report artifact and the same concise table in the Actions summary and engineer/tester handoffs. Every known verification component appears exactly once in one mutually exclusive bucket:

  • rerun: current command, result/counts, evidence ID, input/state digest;
  • reused: source run/evidence ID, original producer/reviewer, age/expiry, matching relevant-input/environment/render digests;
  • skipped: reason and proof; this cannot satisfy a candidate-required component;
  • not_applicable: graph node/edge proof explaining why the component is outside the closure.

Also report canonical base/head, full/focused decision and reasons, direct/downstream nodes, graph/policy/state digests, screenshot status per route/viewport (fresh, reused, or not applicable), artifact locations/digests, and invalid evidence that caused a rerun. Missing categories, duplicate category membership, inconsistent counts, or a report/envelope digest mismatch fails the gate.

Implementation scope

  • Extend ci/ with the graph, schema, canonical digest/evidence validation, render-impact, aggregate planning, reporting, and scheduled-state logic. Keep policy logic out of workflow shell snippets.
  • Extend unit/contract tests under tests_ci/ with deterministic fixtures and golden digest vectors.
  • Update push and scheduled workflows to consume the same plan, publish retained evidence artifacts, expose the four buckets in summaries, and preserve least privilege, concurrency, source/image coherence, and the existing deployment gate.
  • Add uv-backed local Make entry points:
    • make verification-plan VERIFY_BASE_SHA=<sha> VERIFY_HEAD_SHA=<sha> [VERIFY_EVIDENCE_DIR=<dir>]
    • make verification-run VERIFY_PLAN=<path>
    • make verification-full
    • make verification-evidence-check VERIFY_PLAN=<path>
    • make verification-report-check VERIFY_REPORT=<path>
      Existing make test, make test-ci, make test-playwright-core, and make test-playwright remain usable and are invoked by the new orchestration rather than reimplemented.
  • Update _docs/ci/change-selective-ci.md, _docs/PROCESS.md, and the engineer/tester handoff templates with the ownership, evidence, screenshot, reporting, escalation, retention, and role-separation rules.
  • Store local plans, envelopes, screenshots, and scratch results only below .tmp/. CI evidence stays in retained Actions artifacts. Do not commit generated evidence.

Non-goals

Acceptance criteria

  • One schema-validated ownership graph drives local, push, schedule, and handoff planning; its initial app closures are exactly the Run change-selective CI with four-hour full regression #104 table above.
  • Graph errors, unowned/ambiguous paths, multiple app roots, unknown impact, incomplete source ranges, and every full-trigger row above select fresh full regression.
  • The full/focused, browser, container/specialized, and screenshot decisions match the trigger matrix and carry auditable reason codes.
  • Evidence envelopes contain the required source, relevant-input/test/tool/config/environment, provenance, result, artifact, expiry, and invalidation fields with canonical SHA-256 golden-vector coverage.
  • Only completed-success evidence from an allowed origin and within its validity class can satisfy a required component; a later failure/cancellation/stale result invalidates older success.
  • Render changes cause fresh affected desktop/mobile screenshots; unchanged render fingerprints permit only exact-digest, unexpired, independently inspected screenshot reuse.
  • Large content/catalog inputs are covered exhaustively by deterministic artifacts/invariants, and their report distinguishes this from visual coverage.
  • The independent tester recomputes and verifies selection/reuse; engineer self-approval cannot satisfy the tester or CI gate.
  • Every report separates rerun/reused/skipped/not-applicable without overlap; required skipped/missing components fail candidate gating.
  • The four-hour scheduler runs fresh full regression only for a changed/unknown state or after a non-successful attempt, and reports a validated unchanged-state skip otherwise.
  • Local Make targets and both workflows use the same planner/validator and uv-backed environment; CI retains inspectable plans/envelopes/reports without secrets.
  • Existing Run change-selective CI with four-hour full regression #104 classifier, gate, scheduler, full Django, core Playwright, and production-container tests remain green, and the new workflow cannot deploy from a partial or invalid evidence set.
  • CI/process documentation gives an operator enough detail to reproduce a plan, validate evidence, interpret every reason code, and escalate to full regression.

Mandatory negative and boundary tests

  • Unknown path, ambiguous ownership, dangling/cyclic graph, malformed policy, cross-app change, shallow/missing base, and hostile filenames (spaces, Unicode, tabs/newlines, leading dash) all select full safely.
  • Old and new sides of rename/copy/delete changes participate in impact and digest calculation.
  • One-bit changes to source, tests, fixture, lock/tool, config, environment, render input, artifact, or envelope each reject reuse for the affected component.
  • Unknown schema/algorithm, digest prefix, type confusion, missing artifact, forged origin, expired result, non-success result, and later failed/cancelled/stale/timed-out attempt each reject reuse and cannot fall back to older success.
  • A valid unchanged component can be reused while an affected neighbor reruns; a transitive downstream edge makes the neighbor rerun.
  • Every force-full category reruns fresh full regression even when an otherwise matching prior full envelope exists.
  • A non-render backend change reuses or marks visual evidence N/A only after exact render-fingerprint validation; template/CSS/JS/data-shape/navigation and screenshot-harness changes require fresh screenshots.
  • Value-only large-content changes prove counts/identity/order/uniqueness/URL and metadata invariants without one-page-at-a-time manual checks; a data-shape change flips to fresh browser/screenshots.
  • Scheduler tests cover identical state, metadata-only commit with identical state, changed tree, changed graph/tool/environment, no anchor, malformed/missing artifact, bounded history, API failure, and a later unsuccessful run.
  • Report validator rejects missing, duplicate, contradictory, or unproved bucket entries and any required component in skipped/unproved not_applicable.
  • Two plans for the same inputs are byte-identical apart from explicitly excluded volatile metadata; golden digest vectors work across the supported local/CI platforms.

Playwright and screenshot verification

The implementation itself is an internal CI/process change, so product-page screenshots are not required merely because these files changed. The tester must nevertheless exercise the browser decision contract with deterministic fixtures:

  1. template or CSS fixture change -> full browser plus fresh desktop/mobile captures;
  2. rendered data-shape or URL/navigation change -> affected flows plus fresh desktop/mobile captures;
  3. backend-only non-render change -> no fresh visual capture, with exact-digest reuse or explicit N/A;
  4. value-only large-content change -> deterministic invariant artifact and valid prior visual reuse;
  5. missing/tampered/expired/uninspected screenshot evidence -> reuse rejected and fresh capture required.

The tester records viewport, route/state, browser, image digest, render fingerprint, evidence origin, and visual verdict. Core Playwright remains part of fresh full regression; the matrix above identifies when the full Playwright suite is additionally mandatory.

Rollout safety

Ship in fail-closed mode: reuse remains disabled until the new workflow has produced and validated a complete envelope set. If schema migration or artifact retention makes old evidence unavailable, rerun instead of translating it optimistically. Compare the first focused and scheduled decisions against #104's baseline in CI logs; any narrower unexplained result blocks acceptance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Important follow-updocumentationImprovements or additions to documentationenhancementNew feature or requestfoundationArea: foundationoperationsArea: operationssecurityArea: securitytestingArea: testing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions