Release-Mode Artifact Audit

Status: accepted. This document defines the implementation for issue wavenumber-eng/wn-dev-std#13. The durable governance records are core-adr-0006 and core-req-0006.

Problem

The existing artifact, vendor, and release governance checks validate committed catalogs and tracked source-controlled artifact candidates. That default behavior is correct for normal development because local build products are transient and often ignored by Git.

Release signoff needs a stricter path. When a release is being prepared, maintainers need an audit that inspects the actual payload files selected for promotion and proves they match the authored release-channel contract.

CLI Contract

The implemented CLI shape is an audit mode on the existing audit command:

dev-std audit . --scope docs.release --mode release

The default audit mode remains the current source-governance behavior. Release mode is opt-in and normally runs from an explicit release signoff lane after artifacts have been built.

In this implementation, --mode release only changes the behavior of the selected docs.release scope. Other scopes run with their normal behavior. A call with no --scope, or with --scope all, selects docs.release as part of the full audit and therefore runs release payload inspection. A call that selects only non-release scopes accepts the mode but performs no release payload inspection.

Catalog Contract

Release artifact declarations live under the release channel that promotes them. The contract uses nested [[channels.promoted_artifacts]] tables in docs/governance/release.toml.

[[channels]]
id = "pypi"
kind = "pypi"
status = "active"
owner = "release"
process_doc = "docs/setup.html"
build_doc = "docs/build.html"

[[channels.promoted_artifacts]]
id = "wheel"
kind = "package_distribution"
paths = ["dist/wn_dev_std-*.whl"]
required = true
build_profile = "release"
license_refs = ["LICENSE"]

Entries may use an exact path or bounded paths pattern set. Required entries must resolve to at least one produced file. Optional entries may be absent but must pass all metadata checks when present.

Date-versioned packages use bounded globs such as dist/wn_dev_std-*.whl rather than committed literal wheel filenames. Literal version, source_commit, and sha256 fields are reserved for pinned or already-promoted payloads where that metadata is known before the catalog change is committed. When declared, version is compared to the audited package version from pyproject.toml, source_commit is compared as a prefix of git rev-parse HEAD, and sha256 is computed from the matched file. A single sha256 requires an exact path or a pattern that resolves to exactly one file.

destination is optional and channel-relative. It does not duplicate the parent channel kind. It may identify a GitHub release asset name, an object-store key prefix, an app/plugin bundle slot, or another sub-destination inside the configured channel.

Validation

Default mode shape-validates any [[channels.promoted_artifacts]] tables without touching local payload files. That validation includes unique artifact ids per channel, known artifact kinds, boolean required values when present, bounded path or pattern declarations, valid license refs, and path containment inside the audited package root.

Release mode validates path containment before reading local files. It fails required missing artifacts, uncataloged files under configured promoted roots, checksum mismatches when sha256 is declared, version mismatches when version is declared, and source-commit mismatches when source_commit is declared. Missing-required and metadata checks are channel-scoped. Uncataloged payload detection is catalog-wide so channels sharing a staging root such as dist/ do not flag each other's declared files.

Native, WASM, app/plugin, and object-store payloads carry target, build-profile, ABI/runtime, destination, and license notes so reviewers can distinguish intentional optional targets from missing release payloads.

Promoted roots are derived from each declared exact path or glob by taking the static directory prefix before the first wildcard. Exact file entries use their parent directory. The audit enumerates files below those roots and matches repository-relative POSIX paths with fnmatch semantics, matching the existing artifact catalog coverage behavior where * can cross directory separators. Uncataloged-payload detection is therefore based on the same catalog-wide pattern semantics used to decide whether a declared artifact is covered.

Workspace Behavior

Workspace roots remain aggregators. A release-mode workspace audit runs the selected release-mode checks inside each registered member package. Each member resolves docs/governance/release.toml and promoted artifact paths relative to its own package root. Workspace-level catalogs are not part of the first implementation.

Non-Goals

The release-mode audit does not upload artifacts, publish releases, sign files, generate checksums, or replace tools such as Twine, GitHub Release upload actions, object-storage sync tools, or package-manager clients. It checks that the produced local payload set is consistent with the authored release catalog.

Implementation Shape

The implementation keeps the mode plumbing small: parse the mode in dev-std audit, thread it through audit execution, and add release-mode artifact inspection to docs.release. Shared artifact kinds live in src/wn_dev_std/artifact_policy.py, and promoted payload declaration and file inspection live in src/wn_dev_std/release_artifacts.py. Tests prove the default mode still ignores local build output while default release governance catches invalid promoted-artifact table shape, and release mode fails missing required artifacts, uncataloged promoted files, stale checksums, version mismatches, source-commit mismatches, path escapes, and shared-root channel regressions.

The implementation slice must update the audited public documentation surfaces together with code: docs/design/audit-standard.html, docs/design/artifact-vendor-governance.html, docs/design/cli.html, CLI help tests, release catalog tests, and version/release metadata when the change is prepared for release.