diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61feecc..f35722e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: # detector reports it against the FIX on a correct tree. What was not # legitimate is that this workflow ran only the -race lane, so # TestScopeBytesAreReadOnceUnderAConcurrentWriter -- the behavioural - # control whose measured numbers are in internal/SKIPPED-CONTROLS.md + # control whose measured numbers are in docs/controls.md # under G4-1 (0 of 200 constructed Scopes mismatched on the shipped tree, # 89 of 200 with the copy deleted, 102 of 200 with the copy replaced by # `rawAlias := raw[:]`) -- was compiled in ZERO CI lanes. Recorded as N3; @@ -109,7 +109,7 @@ jobs: # # This step exists so the ledger is READ on the machine where it says the # most: ubuntu-latest has dpkg, so host.Collect runs for real here and the - # host-inventory link is PROVEN, where on the Windows dev host it is not. + # host-inventory link is PROVEN, where on a host with no package manager it is not. - name: Lane A chain ledger (printed, not just passed) run: go test -count=1 -v -run TestLaneAChain ./test/conformance/lanea/ @@ -318,7 +318,7 @@ jobs: # there" are indistinguishable to a caller, and for a security scanner that is # the worst possible failure -- an empty result reads as a clean repository. # - # internal/SKIPPED-CONTROLS.md item N1 recorded this as OPEN: the test was + # docs/controls.md item N1 recorded this as OPEN: the test was # correctly made fatal-when-opted-in rather than skip-when-opted-in, which # made it honest, but no machine anywhere set the opt-in, so the control was # proven on NOTHING. This job is what closes it. If it is ever removed, that diff --git a/.gitignore b/.gitignore index 9c9557d..a0c5f40 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ research/ plan/ tools/ +# The owner's logo and branding designs -- kept local, never distributed. +assets/branding/ + # Python __pycache__/ *.py[cod] diff --git a/cmd/anvil-dast/doc.go b/cmd/anvil-dast/doc.go new file mode 100644 index 0000000..c177046 --- /dev/null +++ b/cmd/anvil-dast/doc.go @@ -0,0 +1,2 @@ +// Command anvil-dast is the Anvil dynamic-analysis (DAST) binary. +package main diff --git a/cmd/anvil-dast/main.go b/cmd/anvil-dast/main.go index dc2fc7a..1f6e654 100644 --- a/cmd/anvil-dast/main.go +++ b/cmd/anvil-dast/main.go @@ -1,6 +1,4 @@ -// Command anvil-dast is the Anvil dynamic-analysis (DAST) binary. -// -// It ships as a SEPARATE distribution artifact from the core anvil binary, +// anvil-dast ships as a SEPARATE distribution artifact from the core anvil binary, // requires separate installation, and refuses to probe anything without an // explicit attestation. See the two-artifact split for why this is a // separate artifact rather than a configuration flag: a boolean inside a @@ -12,6 +10,7 @@ // // Bootstrap placeholder. The general entrypoint arrives with the live dynamic // tier (plan node live); every request it makes routes through internal/dast/authz. + package main import ( diff --git a/cmd/anvil-dast/pin-templates.go b/cmd/anvil-dast/pin-templates.go index ab31f3d..c4a8a46 100644 --- a/cmd/anvil-dast/pin-templates.go +++ b/cmd/anvil-dast/pin-templates.go @@ -46,6 +46,7 @@ // `code:` template is refused at load time. This job calls it on BOTH trees // and diffs the results, which means the definition of "dangerous" lives in // exactly one place and this file cannot drift from it. + package main import ( diff --git a/cmd/anvil-host-collector/doc.go b/cmd/anvil-host-collector/doc.go new file mode 100644 index 0000000..454be27 --- /dev/null +++ b/cmd/anvil-host-collector/doc.go @@ -0,0 +1,3 @@ +// Command anvil-host-collector runs Anvil's read-only host package collector +// once, writes the resulting inventory to stdout as JSON, and exits. +package main diff --git a/cmd/anvil-host-collector/main.go b/cmd/anvil-host-collector/main.go index be77cc7..f3aa310 100644 --- a/cmd/anvil-host-collector/main.go +++ b/cmd/anvil-host-collector/main.go @@ -1,6 +1,3 @@ -// Command anvil-host-collector runs Anvil's read-only host package collector -// once, writes the resulting inventory to stdout as JSON, and exits. -// // # Why this main package exists // // deploy/systemd/anvil-host-collector.service has always declared @@ -55,7 +52,7 @@ // // RUN IT UNDER THE SHIPPED UNIT, OR REPRODUCE WHAT THE UNIT DOES. Running this // binary as root outside the unit re-opens the rpmdb write side effect -// documented in internal/collector/host's package comment: on a +// documented in the comment at the top of internal/collector/host/collect.go: on a // Berkeley-DB-backed rpmdb, `rpm -qa` creates /var/lib/rpm/__db.001..003 when // the caller can write that directory. The binary cannot prevent that — no rpm // flag suppresses it, and branching on the effective uid is forbidden — so the @@ -72,6 +69,7 @@ // is that a run never reports a silent "clean", and "we could not look" has // to be distinguishable from "there is nothing here" by a caller reading // the exit status alone. + package main import ( @@ -85,7 +83,7 @@ import ( "github.com/Susquehanna-Syntax/Anvil/internal/collector/host" ) -// Exit statuses, documented in the package comment above. +// Exit statuses, documented in the comment at the top of this file. const ( exitCollected = 0 exitFailed = 1 diff --git a/cmd/anvil-host-collector/main_test.go b/cmd/anvil-host-collector/main_test.go index 84c6edd..03584a2 100644 --- a/cmd/anvil-host-collector/main_test.go +++ b/cmd/anvil-host-collector/main_test.go @@ -40,7 +40,7 @@ func TestTheBinaryRefusesEveryArgument(t *testing.T) { // On a Linux host with a package manager this is the host collector's stop condition and // the read-only-boundary review's "confirmation the binary runs successfully as // a non-root user in the test fixture" — the thing that could not be given -// before, because there was no binary. Everywhere else (a Windows development +// before, because there was no binary. Everywhere else (a non-Linux // host, a Linux container with no package manager) the collector reports no // package manager, which is exit status 3 WITH an inventory: the coverage // report is the point, and this asserts it is emitted rather than swallowed. diff --git a/cmd/anvil/doc.go b/cmd/anvil/doc.go new file mode 100644 index 0000000..707c955 --- /dev/null +++ b/cmd/anvil/doc.go @@ -0,0 +1,2 @@ +// Command anvil is the Anvil core binary. +package main diff --git a/cmd/anvil/main.go b/cmd/anvil/main.go index 7a27616..50069d8 100644 --- a/cmd/anvil/main.go +++ b/cmd/anvil/main.go @@ -1,5 +1,3 @@ -// Command anvil is the Anvil core binary. -// // Anvil finds vulnerabilities in Linux servers and code repositories and // proposes fixes. This binary is the core artifact: Lane A (deterministic // SBOM/host package matching), Lane B (first-party source detection), the @@ -15,6 +13,7 @@ // Bootstrap placeholder. The real entrypoint — the anvil scan and // anvil daemon --loop subcommands wired to internal/scanctl and // internal/queue — is plan node cli (plan/anvil.html). + package main import ( diff --git a/internal/SKIPPED-CONTROLS.md b/docs/controls.md similarity index 97% rename from internal/SKIPPED-CONTROLS.md rename to docs/controls.md index 56a346f..fc25ba2 100644 --- a/internal/SKIPPED-CONTROLS.md +++ b/docs/controls.md @@ -41,14 +41,22 @@ be listed here. ## Method The **"skips here"** column is **measured, not reasoned**: `go test -count=1 -v -./...` was run on the Windows dev host and the `--- SKIP` lines were read out of -the output. The **"skips in CI"** column *is* reasoned, from the condition plus +./...` was run on the development machine and the `--- SKIP` lines were read out +of the output. Development moved from a Windows 11 machine to a Debian machine in +September 2026; the column was re-measured on Debian on 2026-10-03, and where a +row differs between the two the entry says which. The **"skips in CI"** column *is* reasoned, from the condition plus `.github/workflows/ci.yml` (`runs-on: ubuntu-latest`, `go test -race -count=1 ./...`, no Trivy install, no `ANVIL_TRIVY_E2E`, no acquired licence bodies — CI is always a fresh clone). Where the reasoning does not settle it, the column says so rather than guessing. -- Host measured: `go1.26.5 windows/amd64`, Windows 11, non-elevated, +- Host measured on 2026-10-03: `go1.26.5 linux/amd64`, Debian 13, kernel + 6.12.107, uid 1000 (not root). `dpkg-query` is present and lists 3,467 + packages; `rpm`, `apk`, `nft`, `docker`, `podman`, `runsc`, `nuclei`, `zap.sh`, + `java`, `trivy` and `opengrep` are absent. `gcc` is present, so `-race` runs + locally. An unprivileged user and network namespace can be created + (`unshare -rn true` succeeds). +- Host first measured: `go1.26.5 windows/amd64`, Windows 11, non-elevated, Developer Mode **off**. - Suite state after the changes below: `gofmt` clean, `go vet` clean, `go build ./...` clean, `go test -count=1 ./...` **green** (17 packages). @@ -667,12 +675,13 @@ and all of it is tested: passes, so the first half is not passing because the function refuses everything. **The stop condition's FIRST clause — "Default-deny ruleset installs correctly on a real target fixture" — is NOT met and cannot be met - on this host**: nothing here installs a ruleset anywhere, and item (1) below + on the development machine** (Debian, measured 2026-10-03: no `nft` binary, uid + 1000): nothing here installs a ruleset anywhere, and item (1) below is what would settle it. This entry previously claimed the whole stop condition was met, which is how a gap ships. - `SystemCommander` **refuses** on any non-Linux GOOS and - `TestSystemCommanderRefusesOffLinux` asserts that refusal on this host. There - is no no-op Commander, so there is no path by which Windows returns "contained". + `TestSystemCommanderRefusesOffLinux` asserts that refusal wherever GOOS is not + linux. There is no no-op Commander, so there is no path by which Windows returns "contained". **That guard now runs in a CI lane**: `containment-non-linux` in `.github/workflows/ci.yml` runs the package on `windows-latest`, asserts the named test actually executed and passed, then DELETES the @@ -684,9 +693,12 @@ and all of it is tested: ### What it does not prove, and exactly what would settle it Everything above is a statement about Anvil's decision logic. None of it is a -statement about a kernel. Windows has no network namespaces and no nftables; -WSL2 is present on the dev host and is **not** the target runtime, so it does -not settle this either. +statement about a kernel. The Debian development machine (measured 2026-10-03) +has network namespaces and lets an unprivileged process create a user and +network namespace, but `nft` is not installed and the suite runs as uid 1000, so +nothing installs a ruleset and nothing here settles this either. Installing +nftables is the owner's decision; with it, item (1) below may be runnable inside +an unprivileged user namespace on this machine, which has not been tried. What would settle it, in order of decreasing cost: @@ -822,7 +834,8 @@ against. **Docker is not installed on the host this packet was written on.** Measured, not assumed: `Get-Command docker` and `Get-Command runsc` both return nothing -on `go1.26.5 windows/amd64`, Windows 11. +on `go1.26.5 windows/amd64`, Windows 11. Re-measured 2026-10-03 on the Debian +development machine: `docker`, `podman` and `runsc` are still absent. **Nothing in the tree implements the `Docker` interface.** A repository-wide grep for `ComposeUp` and `EngineInfo(` finds the interface, its call sites in @@ -1046,7 +1059,8 @@ container engine**, and this entry is the standing record of that. ### (a) nuclei is not installed, and no adapter exists -**Measured** on the development host: `Get-Command nuclei` finds nothing, and +**Measured** on the Windows development machine, and again on 2026-10-03 on the +Debian one (`command -v nuclei` finds nothing): no engine binary, and `go list -m all` contains no `projectdiscovery` module. `SystemEngine()` therefore returns `*EngineUnavailableError` on **every** host — it never returns a no-op engine — and `ScanResult.AssertNotSilentlyEmpty` refuses to let @@ -1256,7 +1270,8 @@ the same commit. **What would settle it, and what stops it here.** The plan's own suggestion is one `docker stats` run during a representative scheduled scan. -**MEASURED 2026-08-22, PowerShell, on the development host:** +**MEASURED 2026-08-22, PowerShell, on the Windows development machine** (re-measured +2026-10-03 on the Debian one: `docker`, `zap.sh` and `java` are all absent there): ``` Get-Command docker -> NOT FOUND @@ -1999,7 +2014,7 @@ consequence. |---|---| | **File** | `internal/collector/host/collect_test.go:3967` | | **Trigger** | `runtime.GOOS != "linux"` | -| **Skipped here?** | **YES — measured.** `no native package manager on windows` | +| **Skipped here?** | **No — measured 2026-10-03 on Debian**: `dpkg-query` is present and the test enumerates the host. It skipped on the Windows machine (`no native package manager on windows`) | | **Skips in CI?** | No. `ubuntu-latest` is Linux | | **Property unverified** | Real `dpkg-query`/`rpm`/`apk` enumeration against a live host | | **Security control?** | No — a collector coverage claim | @@ -2011,7 +2026,7 @@ consequence. |---|---| | **File** | `internal/collector/host/collect_test.go:3976` | | **Trigger** | Linux, but none of `dpkg-query`/`rpm`/`apk` resolves | -| **Skipped here?** | No — unreachable on Windows (L1 returns first) | +| **Skipped here?** | No — measured 2026-10-03: `dpkg-query` resolves on the Debian machine | | **Skips in CI?** | No. `ubuntu-latest` ships `dpkg-query` | | **Property unverified** | As L1 | | **Security control?** | No | @@ -2023,7 +2038,7 @@ consequence. |---|---| | **File** | `internal/collector/host/collect_test.go:3462` | | **Trigger** | `runtime.GOOS != "windows" && os.Geteuid() == 0` | -| **Skipped here?** | No — measured. `os.Geteuid()` is `-1` on Windows, so the condition is short-circuited | +| **Skipped here?** | No — measured 2026-10-03: the suite runs as uid 1000, so the condition is false (on Windows `os.Geteuid()` is `-1` and the GOOS check short-circuits it) | | **Skips in CI?** | No. GitHub's `ubuntu-latest` runs as the non-root `runner` user. **Would** skip in a root container | | **Property unverified** | Successful enumeration under a non-root UID | | **Security control?** | Partly — the root-free-by-design claim | @@ -2154,11 +2169,11 @@ missing artefact and the command that produces it. | Hazards closed by a test-only change | 9 (7 sites removed, 2 narrowed) | | Hazards needing a non-test change | 4 (N1 closed by CI, N2 closed elsewhere, N3 closed, N4 closed) | | Legitimate skips, left in place | 11 | -| Skips that fire on the Windows dev host | 4 tests / 15 subtest lines | +| Skips that fire on the development machine (Debian, 2026-10-03) | 4 tests / 14 SKIP lines (L4, L5, L7 ×11, L11) | | `t.SkipNow` sites | 0 | -Skips still firing on this host, all classified LEGITIMATE above: -`TestCollectAgainstTheRealHost` (L1), `TestRealTrivyScansAFixtureRepo` (L4), +Skips still firing on the development machine (Debian, measured 2026-10-03), all +classified LEGITIMATE above: `TestRealTrivyScansAFixtureRepo` (L4), `TestBothConsumersAgree` (L5), `TestPinnedLicenceBodiesMatchTheirPins` ×11 (L7), `TestXVM3RelatedLocationsAreCapped/loc0_rel3000` (L11). @@ -2213,8 +2228,9 @@ What remains open in U4 and U5 is the **tooling**: no Nuclei engine and no ZAP on any host or CI lane here, and ZAP's JVM footprint still unmeasured. U1 (`internal/dast/containment`, network containment) is the -highest-stakes of them: the package is green on Windows and has never run -against a Linux kernel. See its entry for the privileged Linux CI lane that +highest-stakes of them: the package is green on Windows and on the Debian +development machine and has never installed a ruleset into a kernel (no `nft` +binary there, measured 2026-10-03). See its entry for the privileged Linux CI lane that would close it, and U1a/U1b for the two things that lane would still not settle on its own. diff --git a/internal/mirror/accelerator/REVIEW-accelerator.md b/docs/reviews/accelerator.md similarity index 99% rename from internal/mirror/accelerator/REVIEW-accelerator.md rename to docs/reviews/accelerator.md index c642990..68813b5 100644 --- a/internal/mirror/accelerator/REVIEW-accelerator.md +++ b/docs/reviews/accelerator.md @@ -4,7 +4,7 @@ **This was a SAME-FAMILY critic.** The packet routes this review to an OpenCode `openai/gpt-5.5-fast` route; that route is withdrawn (plan/design/routing.md OWNER -DECISION, 2026-08-07). This review was performed by a Claude subagent — the same +DECISION, 2026-08-07). This review was performed by an automated review agent from the same model family that produced the accelerator. Correlated blind spots are therefore *not* excluded, and this verdict must never be recorded as "cross-family critic: PASS". Compensation applied: every claim was checked against the file, the suite was diff --git a/internal/match/REVIEW-comparator.md b/docs/reviews/comparator.md similarity index 99% rename from internal/match/REVIEW-comparator.md rename to docs/reviews/comparator.md index 4820c95..354196e 100644 --- a/internal/match/REVIEW-comparator.md +++ b/docs/reviews/comparator.md @@ -423,7 +423,7 @@ the package's own guards: returns nothing (`TestNoSourceFileReachesForAClockOrARandomSource`, re-run and independently reproduced). - `grep -rn "t.Skip" internal/match/` — **none**. No new entry is owed to - `internal/SKIPPED-CONTROLS.md`. + `docs/controls.md`. - Both of the package's own guards (G1 import allowlist, G5 dependency graph) carry working RED controls, and G5's negative control genuinely observes `modernc.org/…` under `internal/ingest/cache`. These are real guards, not decorative ones. diff --git a/internal/record/REVIEW-contract-and-fingerprint.md b/docs/reviews/contract-and-fingerprint.md similarity index 99% rename from internal/record/REVIEW-contract-and-fingerprint.md rename to docs/reviews/contract-and-fingerprint.md index b35d52e..597f026 100644 --- a/internal/record/REVIEW-contract-and-fingerprint.md +++ b/docs/reviews/contract-and-fingerprint.md @@ -12,7 +12,7 @@ the spine's corrected-requirements, record, safety and Go control-plane sections ## 0. WHICH GUARANTEE THIS DOCUMENT ACTUALLY PROVIDES — READ THIS FIRST -**This was a SAME-FAMILY critic.** The critic and both implementers (the record contract, the fingerprint) are Anthropic models. +**This was a SAME-FAMILY critic.** The critic and both implementers (the record contract, the fingerprint) are models from the same family. `plan/design/routing.md` originally required a **different model family** here precisely so that a shared blind spot could not survive review; the owner withdrew external routes on 2026-08-07 because running them means copying `plan/` — deliberately private — to a third-party provider. See the OWNER DECISION diff --git a/internal/scanctl/REVIEW-controller-core.md b/docs/reviews/controller-core.md similarity index 100% rename from internal/scanctl/REVIEW-controller-core.md rename to docs/reviews/controller-core.md diff --git a/internal/record/lanea/REVIEW-emission.md b/docs/reviews/emission.md similarity index 99% rename from internal/record/lanea/REVIEW-emission.md rename to docs/reviews/emission.md index 0ff1f3f..df510b8 100644 --- a/internal/record/lanea/REVIEW-emission.md +++ b/docs/reviews/emission.md @@ -3,8 +3,8 @@ **Verdict: FAIL.** One blocker, two majors, three minors. **This was a SAME-FAMILY critic.** The packet routes this review to OpenCode `openai/gpt-5.5`; that route is -withdrawn per the OWNER DECISION block atop `plan/design/routing.md`. This review was performed by a Claude -subagent — the same family that wrote record emission — and therefore carries none of the independence the +withdrawn per the OWNER DECISION block atop `plan/design/routing.md`. This review was performed by an automated +review agent from the same model family that wrote record emission, and therefore carries none of the independence the cross-family guard was written to buy. Do **not** record this as "cross-family critic: PASS". To compensate, every claim below is backed by a probe I wrote and ran against the committed code, not by reading record emission's own tests. Nine probes were written; six passed, three failed. The probes were deleted diff --git a/internal/record/REVIEW-queue-and-read-path.md b/docs/reviews/queue-and-read-path.md similarity index 99% rename from internal/record/REVIEW-queue-and-read-path.md rename to docs/reviews/queue-and-read-path.md index b4da0c9..68a8b83 100644 --- a/internal/record/REVIEW-queue-and-read-path.md +++ b/docs/reviews/queue-and-read-path.md @@ -19,7 +19,7 @@ block dated 2026-08-07 at the top of `routing.md` withdrew all OpenCode/OpenRout because running one copies private repository contents to a third-party provider; this review's packet still says `OpenCode route (openai/gpt-5.5)` and that route is dead. -The replacement is an Anthropic subagent given an explicit instruction to **refute rather than +The replacement is a same-family review agent given an explicit instruction to **refute rather than assess**, with named failure modes to hunt. That narrows the shared-blind-spot gap; it does not close it. **Do not read this file as "cross-family critic: PASS."** It is a same-family critic, and a later reader deciding whether the cross-family gate has been met should treat it as diff --git a/internal/collector/host/REVIEW-read-only-boundary.md b/docs/reviews/read-only-boundary.md similarity index 100% rename from internal/collector/host/REVIEW-read-only-boundary.md rename to docs/reviews/read-only-boundary.md diff --git a/internal/ingest/sanitize/REVIEW-sanitizer.md b/docs/reviews/sanitizer.md similarity index 100% rename from internal/ingest/sanitize/REVIEW-sanitizer.md rename to docs/reviews/sanitizer.md diff --git a/internal/handoff/REVIEW-sealing-claims-masking.md b/docs/reviews/sealing-claims-masking.md similarity index 100% rename from internal/handoff/REVIEW-sealing-claims-masking.md rename to docs/reviews/sealing-claims-masking.md diff --git a/internal/buildpin/pin.go b/internal/buildpin/pin.go deleted file mode 100644 index a971b2b..0000000 --- a/internal/buildpin/pin.go +++ /dev/null @@ -1,25 +0,0 @@ -// Package buildpin holds Phase 0 dependency pins alive in the module graph -// until the packages that actually use them exist. -// -// The spine's Go control-plane decision mandates modernc.org/sqlite, and -// plan/design/spine-language.md's source table pins it at v1.56.0. plan/design/first-plan.md -// Phase 0 requires that dependency be added at bootstrap. But nothing imports -// it yet -- neither the store schema nor the migration runner's FTS5 startup guard existed -- -// and `go mod tidy` removes a module that no package in the module imports. -// Without this file, CI's tidy check would silently delete the pin the -// bootstrap was supposed to establish. -// -// DELETE THIS PACKAGE when internal/store lands. It has no runtime -// purpose and must never be imported by anything other than itself. -package buildpin - -import ( - // modernc.org/sqlite: the cgo-free SQLite driver the spine's Go - // control-plane decision mandates. Do NOT substitute mattn/go-sqlite3 -- it - // needs cgo and a C toolchain, which breaks the single static binary and - // the cross-compilation matrix. Verified at bootstrap against this exact - // version: SQLite 3.53.3, FTS5 virtual tables and MATCH queries both work. - // Licence read as a file body per the spine's licence section: - // BSD-3-Clause. - _ "modernc.org/sqlite" -) diff --git a/internal/collector/host/collect.go b/internal/collector/host/collect.go index 274aa40..4132df2 100644 --- a/internal/collector/host/collect.go +++ b/internal/collector/host/collect.go @@ -1,7 +1,4 @@ -// Package host is Anvil's READ-ONLY host package collector (plan node -// hostcollector; design in plan/design/lane-a.md). -// -// It enumerates the packages a Linux host has installed by asking the native +// The collector enumerates the packages a Linux host has installed by asking the native // package database — `dpkg-query -W`, `rpm -qa`, `apk list --installed` / // `apk info -v` — reads /etc/os-release, and returns an Inventory. It then // exits. That is the whole product. @@ -121,7 +118,8 @@ // LIMITS OF THIS STATEMENT, so it is not over-read in the other direction: // this is a documented property of rpm's BDB backend, recorded because the // read-only-boundary review's review found the unconditional claim and could -// not reproduce the behaviour on a Windows development host. It has NOT been +// not reproduce the behaviour on the Windows machine it ran on, and the Debian +// development machine (measured 2026-10-03) has no rpm at all. It has NOT been // reproduced by this repository on a BDB host, and the sqlite and ndb backends' // sidecar behaviour (`rpmdb.sqlite-wal`, `-shm`) has not been examined at all. // Nothing is asserted about them in either direction. @@ -164,6 +162,7 @@ // carrying a SQL driver. The `finding`-column vocabulary it must agree with is // asserted against cache's DDL by a TEST-ONLY import instead, so drift is // caught without the dependency. + package host import ( @@ -201,7 +200,7 @@ import ( // and must not try. const RemediableByAgent = false -// ReadOnly records, in the emitted provenance, the claim the package comment's +// ReadOnly records, in the emitted provenance, the claim the file comment's // "WHAT READ-ONLY CLAIMS HERE" section makes and no more: NO MUTATING PACKAGE // MANAGER COMMAND LINE IS EXPRESSIBLE BY THIS COLLECTOR. It does not claim // that a run changes no byte on the host — see RPMDB WRITE SIDE EFFECT in that @@ -219,7 +218,7 @@ const InventorySchemaVersion = 1 // Collector is the `finding.collector` value for rows derived from this // collector. It duplicates internal/ingest/cache's CollectorHost by VALUE -// rather than by import — see the package comment on why the SQLite-linking +// rather than by import — see the comment at the top of this file on why the SQLite-linking // cache package is not a dependency of a production-server collector — and // TestCollectorValueMatchesTheCacheSchema imports cache from the test binary // to prove the two have not drifted. diff --git a/internal/collector/host/collect_test.go b/internal/collector/host/collect_test.go index 4f9829e..a33e425 100644 --- a/internal/collector/host/collect_test.go +++ b/internal/collector/host/collect_test.go @@ -4142,7 +4142,7 @@ var permittedGraphEdges = map[graphEdge]string{ {"path/filepath", "syscall"}: "filepath consults the platform's path rules; resolveBinary joins the constant search directories with it", {"internal/syscall/unix", "syscall"}: "the standard library's per-family syscall shims on linux and darwin", {"internal/syscall/execenv", "syscall"}: "the standard library's environment plumbing for a child process, pulled in by os on every platform", - {"internal/syscall/windows", "syscall"}: "the standard library's Win32 shims; present only on the Windows development host, never on a target", + {"internal/syscall/windows", "syscall"}: "the standard library's Win32 shims; present only in a Windows build, never on a target", {"internal/syscall/windows/registry", "syscall"}: "time's Windows time-zone lookup reads the registry; Windows-only", {"internal/filepathlite", "syscall"}: "filepath's allocation-free core on Windows; Windows-only", {"crypto/internal/sysrand", "syscall"}: "the kernel entropy source, reached by map iteration seeding; linux-only in this graph", @@ -4348,8 +4348,8 @@ func TestTheImportGraphGuardCatchesASpawningDependency(t *testing.T) { // TestCollectAgainstTheRealHost runs the actual exec path when there is a // package manager to run it against. It is SKIPPED on any host without one — -// including the Windows development host — so the evidence it produces belongs -// to CI on Linux and to the fixture-container run, not to a developer laptop. +// including any non-Linux host. On the Debian development machine it runs +// against dpkg (measured 2026-10-03: 3,467 packages) and in CI on ubuntu-latest. func TestCollectAgainstTheRealHost(t *testing.T) { if runtime.GOOS != "linux" { t.Skipf("no native package manager on %s", runtime.GOOS) diff --git a/internal/collector/host/doc.go b/internal/collector/host/doc.go new file mode 100644 index 0000000..2b9341b --- /dev/null +++ b/internal/collector/host/doc.go @@ -0,0 +1,3 @@ +// Package host is Anvil's READ-ONLY host package collector (plan node +// hostcollector; design in plan/design/lane-a.md). +package host diff --git a/internal/collector/host/rpm.go b/internal/collector/host/rpm.go index b3de0fb..ae813b2 100644 --- a/internal/collector/host/rpm.go +++ b/internal/collector/host/rpm.go @@ -20,7 +20,7 @@ import "strings" // database creates and updates /var/lib/rpm/__db.001..__db.003 when the caller // can write that directory, which in practice means when it runs as root. rpm // offers no flag that suppresses this, and this package will not branch on the -// effective uid to avoid it. collect.go's package comment carries the full +// effective uid to avoid it. the comment at the top of collect.go carries the full // statement, its limits, and the deployment-layer mitigation that is the only // place the behaviour can actually be prevented. // diff --git a/internal/collector/repo/doc.go b/internal/collector/repo/doc.go new file mode 100644 index 0000000..f2ab49b --- /dev/null +++ b/internal/collector/repo/doc.go @@ -0,0 +1,4 @@ +// Package repo is Lane A's repository SCA collector (plan node repocollector; +// design in plan/design/lane-a.md): it runs Trivy over a repository that is already on disk and +// turns Trivy's JSON report into Lane A findings. +package repo diff --git a/internal/collector/repo/trivy.go b/internal/collector/repo/trivy.go index e1dc45f..b7b9c84 100644 --- a/internal/collector/repo/trivy.go +++ b/internal/collector/repo/trivy.go @@ -1,7 +1,3 @@ -// Package repo is Lane A's repository SCA collector (plan node repocollector; -// design in plan/design/lane-a.md): it runs Trivy over a repository that is already on disk and -// turns Trivy's JSON report into Lane A findings. -// // # No model, ever // // The spine's corrected-requirements table: Lane A is "deterministic, zero inference — SBOM/host @@ -48,6 +44,7 @@ // attaches when a release artifact bakes the binary in — that is the container image's // call, not this package's, and this comment exists so the container image finds the fact // rather than re-deriving it. + package repo import ( diff --git a/internal/collector/repo/trivy_test.go b/internal/collector/repo/trivy_test.go index bd24ade..ffa0767 100644 --- a/internal/collector/repo/trivy_test.go +++ b/internal/collector/repo/trivy_test.go @@ -1011,7 +1011,7 @@ func TestRealTrivyScansAFixtureRepo(t *testing.T) { // no binary .............. somebody DID ask, and the install step that was // supposed to provide it did not. This used to // skip, which is the pattern internal/ - // SKIPPED-CONTROLS.md exists for: the one test + // docs/controls.md exists for: the one test // that proves a real scanner does not report a // vulnerable fixture clean would have reported // SUCCESS in exactly the run that was configured diff --git a/internal/dast/authz/doc.go b/internal/dast/authz/doc.go new file mode 100644 index 0000000..033c26e --- /dev/null +++ b/internal/dast/authz/doc.go @@ -0,0 +1,3 @@ +// Package authz is Anvil's authorization kernel: the only thing in the system +// that may turn a proposed network request into a permitted one. +package authz diff --git a/internal/dast/authz/egress_chokepoint_test.go b/internal/dast/authz/egress_chokepoint_test.go index cd892b7..a8f26e7 100644 --- a/internal/dast/authz/egress_chokepoint_test.go +++ b/internal/dast/authz/egress_chokepoint_test.go @@ -430,7 +430,7 @@ func scanRepoForEgress(t *testing.T, root string) EgressScan { // repoRootForGuards returns the module root, or fails. // -// It does not skip when it cannot find the root. internal/SKIPPED-CONTROLS.md +// It does not skip when it cannot find the root. docs/controls.md // records this repository shipping two guards that vanished silently when they // could not run; a build-time invariant that cannot locate the tree it guards // has not passed, it has not run. diff --git a/internal/dast/authz/kernel.go b/internal/dast/authz/kernel.go index 9c3c6da..e4d702c 100644 --- a/internal/dast/authz/kernel.go +++ b/internal/dast/authz/kernel.go @@ -47,7 +47,7 @@ // chain REFUSES at that gate. It does not skip it, log a warning, or treat the // absence as a pass. // -// That is not defensiveness for its own sake. internal/SKIPPED-CONTROLS.md +// That is not defensiveness for its own sake. docs/controls.md // records two separate incidents in this repository where a control that could // not run reported success, and names the shape precisely: "a guard that // vanishes silently when it cannot run is worse than no guard, because the diff --git a/internal/dast/authz/kernel_depgraph_test.go b/internal/dast/authz/kernel_depgraph_test.go index d57780b..531cd27 100644 --- a/internal/dast/authz/kernel_depgraph_test.go +++ b/internal/dast/authz/kernel_depgraph_test.go @@ -55,7 +55,7 @@ // half it PROVED and which it could not, and which becomes a real two-sided // measurement the moment cmd/anvil-dast links the kernel. It is not fixed by // fabricating a consumer: a consumer invented to make a gate green is the -// exact shape internal/SKIPPED-CONTROLS.md records this repository shipping +// exact shape docs/controls.md records this repository shipping // twice. package authz @@ -73,7 +73,7 @@ import ( // records it as an ImportGraph. // // It fails rather than skips when the toolchain is unavailable. -// internal/SKIPPED-CONTROLS.md names the failure this avoids: a dependency +// docs/controls.md names the failure this avoids: a dependency // shape guard in this repository skipped whenever `go list` could not run, // "i.e. in exactly the hermetic environments it mattered in". // diff --git a/internal/dast/authz/kernel_test.go b/internal/dast/authz/kernel_test.go index cdca7c9..4e4495b 100644 --- a/internal/dast/authz/kernel_test.go +++ b/internal/dast/authz/kernel_test.go @@ -412,7 +412,7 @@ func TestChainRefusesEveryMissingGateIndividually(t *testing.T) { if r.Permits() { t.Fatalf("the chain permitted with %s missing. A gate with no "+ "implementation compiled in is a REFUSAL, never a skipped step: "+ - "internal/SKIPPED-CONTROLS.md records two incidents in this "+ + "docs/controls.md records two incidents in this "+ "repository where a control that could not run reported success", missing) } diff --git a/internal/dast/authz/phase0_build.go b/internal/dast/authz/phase0_build.go index 707a2d6..eab760e 100644 --- a/internal/dast/authz/phase0_build.go +++ b/internal/dast/authz/phase0_build.go @@ -21,7 +21,7 @@ // THE FACT TYPES REFUSE TO BE FABRICATED, AND REFUSE TO BE EMPTY // =========================================================================== // -// internal/SKIPPED-CONTROLS.md opens with two incidents in this repository +// docs/controls.md opens with two incidents in this repository // where a control that could not run reported success — a symlink test that // skipped on Windows while a directory junction walked through the guard it // protected, and a dependency-shape guard that skipped whenever `go list` @@ -372,7 +372,7 @@ func isStdlibPackage(p string) bool { // // That is a vacuous positive, and it is written down rather than papered over. // The gate is not fabricated a consumer to satisfy it — a consumer invented to -// make a gate green is the exact shape internal/SKIPPED-CONTROLS.md records +// make a gate green is the exact shape docs/controls.md records // this repository shipping twice. The kernel's build-time guard owns the real // positive control: once cmd/anvil-dast links the kernel, gate 1 needs BOTH // graphs — anvil-dast MUST contain internal/dast/authz and anvil MUST NOT — so diff --git a/internal/dast/authz/phase0_build_test.go b/internal/dast/authz/phase0_build_test.go index aa158a4..280e047 100644 --- a/internal/dast/authz/phase0_build_test.go +++ b/internal/dast/authz/phase0_build_test.go @@ -70,7 +70,7 @@ func mustScan(t *testing.T, files int, sites ...EgressCallSite) EgressScan { // THE FACT TYPES REFUSE AN UNMEASURED MEASUREMENT // =========================================================================== -// TestUnmeasuredFactsAreRefused is the guard internal/SKIPPED-CONTROLS.md was +// TestUnmeasuredFactsAreRefused is the guard docs/controls.md was // written about: "a guard that vanishes silently when it cannot run is worse // than no guard, because the green tick is read as an answer." func TestUnmeasuredFactsAreRefused(t *testing.T) { diff --git a/internal/dast/authz/phase1_run.go b/internal/dast/authz/phase1_run.go index 5acdfdb..bc14aab 100644 --- a/internal/dast/authz/phase1_run.go +++ b/internal/dast/authz/phase1_run.go @@ -66,7 +66,7 @@ // behaviour depend on test execution order. // 3. Register a gate 7 that permits whenever the four inputs are well formed. // That is a gate that has never refused anything — the exact shape -// internal/SKIPPED-CONTROLS.md records this repository shipping twice, and +// docs/controls.md records this repository shipping twice, and // the reason the engineering standard "a guard that has never failed has // not been tested" exists. // @@ -111,6 +111,7 @@ // operator genuinely needs to see what was rejected, the value goes into // Evidence through redactUntrusted, which keeps an allowlisted charset and // bounds the length. + package authz import ( @@ -523,7 +524,7 @@ func LoadScopeFile(path string, decl ModeDeclaration) (Scope, GateResult) { // it differ from another, so the harness is a data race by construction, and // `go test -race` reports it against THIS FILE'S COPY on a correct build. It // therefore cannot run in the -race lane without failing a correct tree. -// Recorded in internal/SKIPPED-CONTROLS.md as G4-1, with the literal race +// Recorded in docs/controls.md as G4-1, with the literal race // report. func CheckGate4ScopeFile(raw []byte, decl ModeDeclaration) (Scope, GateResult) { // ONE guard, not two. An earlier draft checked decl.Declared() first and diff --git a/internal/dast/authz/phase1_run_test.go b/internal/dast/authz/phase1_run_test.go index 9d6b0d0..414e285 100644 --- a/internal/dast/authz/phase1_run_test.go +++ b/internal/dast/authz/phase1_run_test.go @@ -2381,7 +2381,7 @@ func TestGate7CannotBeRegisteredAsAGateFunc(t *testing.T) { t.Fatal("registerInto accepted a gate 7 implementation. A vacuous gate 7 — one " + "that permits whenever the four inputs are well formed — is a gate that " + "has never refused anything, which is the exact shape " + - "internal/SKIPPED-CONTROLS.md records this repository shipping twice") + "docs/controls.md records this repository shipping twice") } if !errors.Is(err, ErrRefused) { t.Fatalf("the refusal does not unwrap to ErrRefused: %v", err) diff --git a/internal/dast/authz/phase1_scopebytes_race_test.go b/internal/dast/authz/phase1_scopebytes_race_test.go index 133d734..11845ee 100644 --- a/internal/dast/authz/phase1_scopebytes_race_test.go +++ b/internal/dast/authz/phase1_scopebytes_race_test.go @@ -35,7 +35,7 @@ // So it runs in the ordinary `go test ./...` lane, where it is a real control // with real numbers, and TestGate4ReadsTheCallersScopeBytesExactlyOnce — a // source-level assertion, no goroutines — is what runs in every lane. The -// exclusion is recorded in internal/SKIPPED-CONTROLS.md as G4-1 rather than +// exclusion is recorded in docs/controls.md as G4-1 rather than // hidden behind a t.Skip. // // =========================================================================== diff --git a/internal/dast/authz/phase2_admission.go b/internal/dast/authz/phase2_admission.go index b48ca99..70eba2b 100644 --- a/internal/dast/authz/phase2_admission.go +++ b/internal/dast/authz/phase2_admission.go @@ -49,7 +49,7 @@ // is a safety-section violation wearing a hat. // - Registering a gate 11 that permits whenever the four inputs are well // formed would be a gate that has never refused anything — the exact shape -// internal/SKIPPED-CONTROLS.md records this repository shipping twice. +// docs/controls.md records this repository shipping twice. // // GATE 11 IS ENFORCED IN TWO PLACES INSTEAD, and neither is a chain position. // @@ -89,6 +89,7 @@ // goes through redactUntrusted (phase1_run.go), which keeps an allowlisted // charset and bounds the length, and every parsed value is separately bounded // and charset-restricted at parse time. + package authz import ( @@ -1304,7 +1305,7 @@ func gate10ReservedRanges(target Target, scope Scope, _ Attestation, _ Clock) Ru // // The distinction between "we looked and there was none" and "we did not // look" is the entire reason this type exists rather than a bool. -// internal/SKIPPED-CONTROLS.md names the shape: "a guard that vanishes +// docs/controls.md names the shape: "a guard that vanishes // silently when it cannot run is worse than no guard, because the green tick is // read as an answer." A policy nobody determined permits no path. type RobotsDetermination string @@ -1677,7 +1678,7 @@ func CheckGate11RobotsDeny(policy RobotsPolicy, target Target, path string) Gate return gateFailed(g, ReasonRobotsNotDetermined, "no robots.txt determination was made for this origin. \"We did not look\" and "+ "\"we looked and there was nothing\" produce the same silence and opposite "+ - "conclusions; internal/SKIPPED-CONTROLS.md records two incidents in this "+ + "conclusions; docs/controls.md records two incidents in this "+ "repository where the first was read as the second.") } if policy.Determination() == RobotsUnavailable { diff --git a/internal/dast/authz/phase3_enforcement.go b/internal/dast/authz/phase3_enforcement.go index ffd4b4b..cbe6413 100644 --- a/internal/dast/authz/phase3_enforcement.go +++ b/internal/dast/authz/phase3_enforcement.go @@ -71,6 +71,7 @@ // CapOverrides / ThresholdOverrides, which carry plain numbers, and the only // operation on them is Lower. A config value therefore cannot BE a floor; it // can only be compared against one. + package authz import ( @@ -1866,7 +1867,7 @@ func CheckGate16CircuitBreaker(m *HealthMonitor) GateResult { if !m.Constructed() { return gateFailed(g, ReasonMonitorUnconstructed, "there is no health monitor for this target. A target nobody is monitoring is "+ - "not a target known to be healthy — internal/SKIPPED-CONTROLS.md records "+ + "not a target known to be healthy — docs/controls.md records "+ "two incidents in this repository where \"we did not look\" was read as "+ "\"we looked and it was fine\".") } diff --git a/internal/dast/authz/phase4_disclosure.go b/internal/dast/authz/phase4_disclosure.go index 0b9617d..85d8313 100644 --- a/internal/dast/authz/phase4_disclosure.go +++ b/internal/dast/authz/phase4_disclosure.go @@ -99,7 +99,7 @@ // accepts a "now", so the one instant the caller chose is the one every // comparison in the run is made against. A lie is still spellable; it is no // longer spellable INCONSISTENTLY, and the cost of telling it consistently is -// in this file's closing section and in SKIPPED-CONTROLS G18-2. +// in this file's closing section and in docs/controls.md G18-2. // // types.go's RunClock is sealed at run initiation by an unexported // constructor, exactly as Scope and Attestation are, and @@ -138,7 +138,8 @@ // runs are keyed to different attestation IDs. That is a materially larger and // more visible act than passing a different time.Time to the next call, and it // is as far as a kernel with no trusted clock can go. Recorded in -// internal/SKIPPED-CONTROLS.md as G18-2. +// docs/controls.md as G18-2. + package authz import ( @@ -1581,7 +1582,7 @@ func (r DisclosureRecord) ScopeHash() ScopeHash { return r.key.ScopeHash() } // handoff buffer, and in this tree disclosure state lives NOWHERE. Every // refusal in this section is real and tested; the ALLOW has never been taken // by a caller that actually persisted anything. Recorded in -// internal/SKIPPED-CONTROLS.md as G19-1, with what would settle it. +// docs/controls.md as G19-1, with what would settle it. // // # The residual the declaration leaves even once one exists // diff --git a/internal/dast/authz/phase4_disclosure_test.go b/internal/dast/authz/phase4_disclosure_test.go index d3c87e1..c56f5da 100644 --- a/internal/dast/authz/phase4_disclosure_test.go +++ b/internal/dast/authz/phase4_disclosure_test.go @@ -3111,7 +3111,7 @@ func TestPhase4EmbargoConstantsPinTheRelationsNotOnlyTheValues(t *testing.T) { // NOT a constant-interaction defect and is deliberately not asserted // here: an embargo outliving the attestation that authorised the scan // is expected, and it is what makes the two-run divergent-clock - // residual visible in the audit log at all. See SKIPPED-CONTROLS + // residual visible in the audit log at all. See docs/controls.md // G18-2. } diff --git a/internal/dast/authz/types.go b/internal/dast/authz/types.go index 900f8e5..d3662b2 100644 --- a/internal/dast/authz/types.go +++ b/internal/dast/authz/types.go @@ -1,6 +1,3 @@ -// Package authz is Anvil's authorization kernel: the only thing in the system -// that may turn a proposed network request into a permitted one. -// // The decision flow — Decide, Adjudicate, Authorization, RequireAuthorization — // is documented at the top of kernel.go. This file holds the VOCABULARY: the // types every one of the 21 gates in plan/design/dynamic-tier.md's Authorization Gate @@ -28,6 +25,7 @@ // No net.Dial, no http.Client, no socket construction of any kind — the kernel core's // forbidden actions, and gate 3's whole point. This file imports stdlib only, // and gate 2 (phase0_build.go) is the machine check that keeps it that way. + package authz import ( @@ -78,7 +76,7 @@ var ( ErrCapRaise = fmt.Errorf("%w: configuration may only LOWER a coded cap, never raise it", ErrRefused) // ErrNotMeasured is the Phase 0 refusal: a build-time gate was handed - // facts that were never actually measured. internal/SKIPPED-CONTROLS.md + // facts that were never actually measured. docs/controls.md // records two separate incidents in this repository where a guard that // could not run reported success; a gate that cannot measure must fail, // not pass. @@ -1188,7 +1186,7 @@ func (s Scope) removePaths(host string, port uint16, policy RobotsPolicy, rec Sc // - PermitsPath implies Permits. Everything Permits refuses, this refuses. // - A SCOPE THAT WAS NEVER NARROWED PERMITS NO PATH. "Nobody determined this // origin's robots.txt" and "robots.txt permitted this path" produce the -// same silence and opposite conclusions, and internal/SKIPPED-CONTROLS.md +// same silence and opposite conclusions, and docs/controls.md // records two incidents in this repository where the first was read as the // second. So a path needs a determination that COVERS ITS ORIGIN before it // can be permitted here, and the zero Scope permits nothing at all. diff --git a/internal/dast/containment/doc.go b/internal/dast/containment/doc.go new file mode 100644 index 0000000..47154a3 --- /dev/null +++ b/internal/dast/containment/doc.go @@ -0,0 +1,5 @@ +// Package containment holds the DAST half's network containment: a dedicated +// Linux network namespace whose egress is default-deny under nftables, and an +// assertion probe that re-proves the containment ON EVERY RUN. It also provisions, +// holds and resets the ephemeral target a DAST scan runs against. +package containment diff --git a/internal/dast/containment/netns.go b/internal/dast/containment/netns.go index a7b86db..65f5c2f 100644 --- a/internal/dast/containment/netns.go +++ b/internal/dast/containment/netns.go @@ -1,7 +1,3 @@ -// Package containment holds the DAST half's network containment: a dedicated -// Linux network namespace whose egress is default-deny under nftables, and an -// assertion probe that re-proves the containment ON EVERY RUN. -// // # The design spike the plan asked for // // plan/design/dynamic-tier.md's Open Questions (the network containment entry) leaves the assertion @@ -67,11 +63,13 @@ // that reads the inode through the same `ip netns exec` it later fails to apply // would agree with itself and report contained. // -// # This host cannot prove any of it +// # The development machine cannot prove any of it // -// The development host for this packet is WINDOWS. Linux network namespaces -// and nftables do not exist here, so nothing in this file that touches the -// kernel can be executed, and no test in this package claims otherwise. The +// This packet was written on Windows, where Linux network namespaces and +// nftables do not exist. The Debian machine development moved to has network +// namespaces but no nft binary, and the suite runs as uid 1000 (measured +// 2026-10-03). Nothing in this file that touches the kernel has been executed, +// and no test in this package claims otherwise. The // package is therefore split so that the honest part is testable everywhere: // // - The ruleset is BUILT by a pure function (BuildRuleset) and pinned by @@ -84,7 +82,7 @@ // // SystemCommander REFUSES on any non-Linux GOOS -- it does not return a // no-op that would let AssertContainment return nil. There is no t.Skip in -// this package. See internal/SKIPPED-CONTROLS.md entry U1 for what remains +// this package. See docs/controls.md entry U1 for what remains // unexecuted and exactly what would settle it. // // # Why this package holds no socket @@ -96,6 +94,7 @@ // declaration for the full reasoning; the short version is that the gate was // right, the first draft of this file was wrong, and the seam moved rather // than the gate. + package containment import ( @@ -190,7 +189,7 @@ const ChainName = "egress" // WHAT THIS STILL DOES NOT PROVE: the canary runs under `ip netns exec`, so it // holds a socket IN the namespace and its own dials traverse `output`, not // `forward`. It therefore exercises the chain the TARGET DOES NOT USE. Recorded -// in internal/SKIPPED-CONTROLS.md as U1a, with what would settle it. +// in docs/controls.md as U1a, with what would settle it. // // WHY THERE IS NO `input` CHAIN, stated rather than left as an omission. The // property asserted here is what the target can REACH. `input` is inbound: the @@ -985,7 +984,7 @@ func splitAllowEntries(m *target.Manifest) ([]netip.Prefix, []string, error) { // package's own tests. So booted_clean today means "the container is contained // by gVisor" and does NOT mean "its egress is default-deny". Wiring that is the // integration packet's, not this one's, and it is recorded in -// internal/SKIPPED-CONTROLS.md as U1c so it cannot be forgotten -- including +// docs/controls.md as U1c so it cannot be forgotten -- including // the containment review's criterion that AssertContainment must run BEFORE any probe engine // starts. // @@ -1092,7 +1091,7 @@ const CanarySubcommand = "__anvil-dast-netns-canary" // DefaultCanaryDialTimeout bounds one connect attempt. It is the contract a // ConnectProbe implementation honours. // -// It is short on purpose, and the asymmetry in the package doc is why that is +// It is short on purpose, and the asymmetry in the comment at the top of this file is why that is // safe rather than sloppy: a REACHABLE metadata endpoint is link-local and // answers in well under a millisecond, so a timeout in this range can only // convert a slow "blocked" into a "blocked" -- it can never convert a @@ -1113,7 +1112,7 @@ const CanarySubcommand = "__anvil-dast-netns-canary" // The canary remains the UNTRUSTED half and this package cannot authenticate // it: a substituted binary can write any number it likes. What the check buys // is that the ordinary way this control rots -- an honest probe with too short -// a dialer -- now fails loudly. The rest is internal/SKIPPED-CONTROLS.md U1b. +// a dialer -- now fails loudly. The rest is docs/controls.md U1b. const DefaultCanaryDialTimeout = 2 * time.Second // canaryTimingTolerance is how much short of DefaultCanaryDialTimeout a diff --git a/internal/dast/containment/netns_test.go b/internal/dast/containment/netns_test.go index b24e734..8aa341a 100644 --- a/internal/dast/containment/netns_test.go +++ b/internal/dast/containment/netns_test.go @@ -21,8 +21,10 @@ import ( // What this file can and cannot prove // --------------------------------------------------------------------------- // -// This suite runs on the Windows development host, where Linux network -// namespaces and nftables DO NOT EXIST. It therefore proves the DECISION LOGIC +// This suite was written on Windows, where Linux network namespaces and +// nftables do not exist, and runs on the Debian development machine as uid +// 1000 with no nft binary (measured 2026-10-03). Neither can install a ruleset, +// so it proves the DECISION LOGIC // -- ruleset construction, rule ordering, the deny-set relation with the // authorization kernel's gate 10, and every branch of the canary-report // evaluation -- and it proves that the platform path REFUSES rather than @@ -30,7 +32,7 @@ import ( // kernel drops the packet, or that a real canary in a real namespace reports // what this suite's fake canaries report. // -// That gap is written down, not implied: internal/SKIPPED-CONTROLS.md entry U1 +// That gap is written down, not implied: docs/controls.md entry U1 // names it and names exactly what would settle it. // // There is NO t.Skip in this file. Every test asserts something on every @@ -1595,7 +1597,7 @@ func TestAssertContainmentRefusesANilCommanderAndAnEmptyCanaryPath(t *testing.T) // This test is the clause after the semicolon, and it is met. THE CLAUSE // BEFORE IT IS NOT MET AND CANNOT BE MET ON THIS HOST: there is no real target // fixture, no kernel, no nftables, and nothing here installs a ruleset -// anywhere. internal/SKIPPED-CONTROLS.md entry U1 is the standing record of +// anywhere. docs/controls.md entry U1 is the standing record of // that half, and a privileged Linux CI lane is what would settle it. // // The distinction is not pedantry. An exit criterion recorded as met when half @@ -1762,8 +1764,8 @@ func (p recordingProbe) Attempt(_ context.Context, network, address string) (boo // a verdict. What it does not prove -- stated so the claim matches the // evidence -- is that a Linux kernel delivers the errno a ConnectProbe would // turn into each of these names. That mapping lives with the implementation, -// which holds the socket; nothing on a Windows host can prove it. See -// internal/SKIPPED-CONTROLS.md U1. +// which holds the socket; nothing on a host without that implementation can prove it. See +// docs/controls.md U1. func TestClassifyDialFailureTable(t *testing.T) { cases := []struct { f DialFailure @@ -1958,7 +1960,7 @@ func TestTimeoutIsBoundedAndNonZero(t *testing.T) { // that cannot run on the development host, which is exactly the condition // under which a skip is reached for -- and a skipped containment assertion // lets the package print `ok` while proving nothing about the sandbox. The gap -// goes in internal/SKIPPED-CONTROLS.md (entry U1) instead, where it is read +// goes in docs/controls.md (entry U1) instead, where it is read // rather than passed over. func TestThisFileSkipsNothing(t *testing.T) { src, err := parser.ParseFile(token.NewFileSet(), "netns_test.go", nil, parser.SkipObjectResolution) @@ -1980,7 +1982,7 @@ func TestThisFileSkipsNothing(t *testing.T) { } if strings.HasPrefix(sel.Sel.Name, tok[2:]) { t.Errorf("netns_test.go calls t.%s. If a control genuinely cannot run here, it "+ - "belongs in internal/SKIPPED-CONTROLS.md with what would settle it -- not "+ + "belongs in docs/controls.md with what would settle it -- not "+ "behind a green tick", sel.Sel.Name) } return true diff --git a/internal/dast/containment/provision.go b/internal/dast/containment/provision.go index 969e473..a1f7272 100644 --- a/internal/dast/containment/provision.go +++ b/internal/dast/containment/provision.go @@ -1,6 +1,3 @@ -// Package containment provisions and holds the ephemeral target a DAST scan -// runs against. -// // This file is target provisioning: given the DECLARED manifest that // internal/dast/target already parsed, bring up the operator's Compose project // under gVisor `runsc`, wait on the declared healthcheck with a hard timeout, @@ -38,7 +35,7 @@ // // Docker is not installed on every host that builds Anvil -- it is not // installed on the host this packet was written on, which is recorded in -// internal/SKIPPED-CONTROLS.md as entry U2, along with the three things that +// docs/controls.md as entry U2, along with the three things that // would settle it in order of decreasing cost. The Docker interface below is // the ONE boundary between // this logic and the container runtime, so the decision logic -- the @@ -87,6 +84,7 @@ // asserts the runner was never called. There is no flag, option or environment // variable in this package that relaxes any assertion in it; widening one is a // visible edit to this file. + package containment import ( @@ -173,7 +171,7 @@ const ( // NOTHING ENFORCES THE SPLIT AT THE PHASE BOUNDARY, because the boundary // is inside `docker compose up` and no implementation of the Docker seam // exists. The two budgets are a contract stated in that interface's doc, - // and internal/SKIPPED-CONTROLS.md U2a records that nobody has honoured + // and docs/controls.md U2a records that nobody has honoured // it yet, along with the fixture that would settle it. // // It is a compiled-in constant and not a manifest field on purpose: @@ -578,7 +576,7 @@ func (e *ProvisionError) Provisioning() (record.TargetProvisioning, error) { // // Nothing in this package shells out, opens a socket, or imports a client. A // real implementation of this interface is a separate concern and is NOT -// provided here -- see internal/SKIPPED-CONTROLS.md entry U2 for what remains +// provided here -- see docs/controls.md entry U2 for what remains // unproven without one and exactly what would settle it. // // THE CONTRACT AN IMPLEMENTATION OWES, stated here because the compiler cannot @@ -1062,7 +1060,7 @@ func NewProvisioner(d Docker, repoRoot string) (*Provisioner, error) { // SetupNetns and AssertContainment are separate exported functions and nothing // in the tree calls either. A caller that fires probes on the strength of // booted_clean alone is asserting a network containment nobody established. -// Recorded in internal/SKIPPED-CONTROLS.md as U1c; wiring it belongs to the +// Recorded in docs/controls.md as U1c; wiring it belongs to the // integration packet. // // The order below is the contract, and it is ordered so that nothing starts diff --git a/internal/dast/containment/provision_test.go b/internal/dast/containment/provision_test.go index 15eb7d9..8d4a837 100644 --- a/internal/dast/containment/provision_test.go +++ b/internal/dast/containment/provision_test.go @@ -25,7 +25,7 @@ import ( // Every test in this file drives provisioning through RECORDED SHAPES of what // `docker info`, `docker compose up` and `docker inspect` return. Docker is // not installed on the host this packet was written on -- verified, and -// recorded in internal/SKIPPED-CONTROLS.md as entry U2 with what would settle +// recorded in docs/controls.md as entry U2 with what would settle // it -- so the decision logic is proven here in full and the fidelity of a // real implementation's shapes is not. // @@ -1908,7 +1908,7 @@ func TestTheSealedSnapshotIsTakenByValue(t *testing.T) { // --------------------------------------------------------------------------- // TestEverySkippedControlsPointerResolves: this package defers its unproven -// half to internal/SKIPPED-CONTROLS.md by ENTRY ID, in doc comments a reader is +// half to docs/controls.md by ENTRY ID, in doc comments a reader is // expected to follow. Three of those pointers named an id numbered after the old // plan step -- one per line of provisioning's doc -- that was never filed under // that name; the entry exists as U2. So the one document holding @@ -1917,7 +1917,7 @@ func TestTheSealedSnapshotIsTakenByValue(t *testing.T) { // A pointer nobody follows is a pointer nobody notices is broken, so this // resolves every one of them against the document's actual headings. func TestEverySkippedControlsPointerResolves(t *testing.T) { - const doc = "../../SKIPPED-CONTROLS.md" + const doc = "../../../docs/controls.md" body, err := os.ReadFile(doc) if err != nil { t.Fatalf("this package's doc comments defer to %s and it cannot be read: %v", doc, err) @@ -1932,7 +1932,7 @@ func TestEverySkippedControlsPointerResolves(t *testing.T) { t.Fatalf("no entry headings were found in %s, so every assertion below is vacuous", doc) } - // The id shapes SKIPPED-CONTROLS.md actually uses, as an ALLOWLIST of + // The id shapes docs/controls.md actually uses, as an ALLOWLIST of // prefixes rather than a denylist of everything else a nearby sentence // might contain. Other letter-and-number shapes are deliberately not in it. idIn := regexp.MustCompile(`\b([DUNGHL][0-9]+(?:-[0-9]+)?[a-z]?)\b`) @@ -1947,7 +1947,7 @@ func TestEverySkippedControlsPointerResolves(t *testing.T) { t.Fatalf("reading %s: %v", f, err) } text := string(src) - const marker = "SKIPPED-CONTROLS.md" + const marker = "docs/controls.md" for i := 0; ; { j := strings.Index(text[i:], marker) if j < 0 { @@ -1971,11 +1971,11 @@ func TestEverySkippedControlsPointerResolves(t *testing.T) { } } if checked == 0 { - t.Fatal("no SKIPPED-CONTROLS.md entry pointers were found in this package's sources. " + + t.Fatal("no docs/controls.md entry pointers were found in this package's sources. " + "Either the deferral comments were deleted -- in which case the unproven half is now " + "undocumented -- or this test's pattern no longer matches them and it is a no-op") } - t.Logf("resolved %d SKIPPED-CONTROLS.md entry pointers against %d headings", checked, len(headings)) + t.Logf("resolved %d docs/controls.md entry pointers against %d headings", checked, len(headings)) } // TestNothingInThisPackageReadsConfiguration: plan/design/dynamic-tier.md forbids a @@ -1997,7 +1997,7 @@ func TestNothingInThisPackageReadsConfiguration(t *testing.T) { } // TestThisPackageSkipsNothing: a skipped test still lets the package print ok. -// internal/SKIPPED-CONTROLS.md exists because that has already cost this +// docs/controls.md exists because that has already cost this // repository twice. func TestThisPackageSkipsNothing(t *testing.T) { src, err := os.ReadFile("provision_test.go") @@ -2011,7 +2011,7 @@ func TestThisPackageSkipsNothing(t *testing.T) { for _, forbidden := range []string{tok + "(", tok + "f(", tok + "Now("} { if strings.Contains(string(src), forbidden) { t.Errorf("provision_test.go contains %q. If a control genuinely cannot run "+ - "here, it belongs in internal/SKIPPED-CONTROLS.md with what would "+ + "here, it belongs in docs/controls.md with what would "+ "settle it -- not behind a green tick", forbidden) } } diff --git a/internal/dast/containment/reset.go b/internal/dast/containment/reset.go index c1b12dc..73995b3 100644 --- a/internal/dast/containment/reset.go +++ b/internal/dast/containment/reset.go @@ -66,7 +66,7 @@ // a destroy-and-recreate discards the seed's effects and nothing in this // tree can replay them. Returning an unseeded target and calling it "the // declared initial state" is the silent lie this packet is about. Named, -// with what would settle it, in internal/SKIPPED-CONTROLS.md U3. +// with what would settle it, in docs/controls.md U3. // // # A failed reset stops the run for that target, structurally // @@ -82,6 +82,7 @@ // record.HalfStatusFailed. Its DastStatus() derives through // record.DeriveDastStatus, so this file writes no record enum literal and the // derived status can never be DastStatusCompletedClean. + package containment import ( @@ -548,7 +549,7 @@ func NewResetter(p *Provisioner, m *target.Manifest) (*Resetter, error) { "destroy-and-recreate discards its effects and nothing in this tree can "+ "replay it, so the target this would hand back is NOT the declared initial "+ "state -- it is an unseeded one. Refusing loudly beats returning it and "+ - "calling the next probe independent. internal/SKIPPED-CONTROLS.md U3 names "+ + "calling the next probe independent. docs/controls.md U3 names "+ "what would settle this", ErrRefused, ErrResetSeedNotReplayable, m.Seed.Command) } diff --git a/internal/dast/containment/reset_test.go b/internal/dast/containment/reset_test.go index 7e444d4..6826b50 100644 --- a/internal/dast/containment/reset_test.go +++ b/internal/dast/containment/reset_test.go @@ -1392,7 +1392,7 @@ func TestResetFileSkipsNothing(t *testing.T) { for _, forbidden := range []string{tok + "(", tok + "f(", tok + "Now("} { if strings.Contains(string(src), forbidden) { t.Errorf("reset_test.go contains %q. If a control genuinely cannot run here "+ - "it belongs in internal/SKIPPED-CONTROLS.md with what would settle it -- "+ + "it belongs in docs/controls.md with what would settle it -- "+ "not behind a green tick", forbidden) } } diff --git a/internal/dast/engines/doc.go b/internal/dast/engines/doc.go new file mode 100644 index 0000000..ecfd8e5 --- /dev/null +++ b/internal/dast/engines/doc.go @@ -0,0 +1,4 @@ +// Package engines holds Anvil's probe-engine drivers: one for Nuclei (nuclei.go) +// and one for ZAP (zap.go). A target reaches either engine only by way of an +// authz.Authorization. +package engines diff --git a/internal/dast/engines/nuclei.go b/internal/dast/engines/nuclei.go index c56f9b0..8775155 100644 --- a/internal/dast/engines/nuclei.go +++ b/internal/dast/engines/nuclei.go @@ -1,5 +1,4 @@ -// Package engines holds Anvil's probe-engine drivers. This file is the nuclei driver: the -// Nuclei driver. +// This file is the Nuclei driver. // // # What this driver is for, stated as the failure it prevents // @@ -61,7 +60,7 @@ // available failure, so they are separated here by type, by exit code, and by // a coverage predicate the caller must consult before reporting clean. // -// internal/SKIPPED-CONTROLS.md entry U4 records exactly what remains +// docs/controls.md entry U4 records exactly what remains // unexecuted and what would settle it. There is no t.Skip in this package. // // # What this driver assumes about template provenance: NOTHING @@ -91,6 +90,7 @@ // directory cannot reach a host the kernel did not admit, because it does not // get to name a host at all. It can, at most, cause a request the kernel then // gates like every other request. + package engines import ( @@ -446,7 +446,7 @@ func (e *EngineUnavailableError) ExitCode() int { return ExitCodeArtefactAbsent // does not control, and an engine that re-resolves reopens DNS rebinding. // // That obligation is STATED HERE AND ENFORCED NOWHERE IN THIS FILE. It is a -// contract on the implementer, recorded in internal/SKIPPED-CONTROLS.md U4 as +// contract on the implementer, recorded in docs/controls.md U4 as // the specific thing an integration lane must prove. type Engine interface { // ExecuteCallbackWithCtx runs the plan and calls cb once per result. @@ -1010,7 +1010,7 @@ func LoadTemplates(dir string) ([]Template, []RejectedTemplate, error) { // // This is an ALLOWLIST of two shapes rather than a check for // os.ModeSymlink, and the reason is measured rather than - // theoretical. On this Windows host a DIRECTORY JUNCTION (`mklink + // theoretical. On the Windows machine this was written on, a DIRECTORY JUNCTION (`mklink // /J`, which needs no privilege at all, unlike `mklink /D`) is // reported by filepath.WalkDir as: // @@ -1019,7 +1019,7 @@ func LoadTemplates(dir string) ([]Template, []RejectedTemplate, error) { // — os.ModeIrregular, with the symlink bit CLEAR and IsDir false. A // `d.Type()&os.ModeSymlink != 0` check sees nothing there. That is // the same primitive that walked through internal/mirror/accelerator's - // quarantine guard (internal/SKIPPED-CONTROLS.md H1), and it is why + // quarantine guard (docs/controls.md H1), and it is why // the question here is "is this one of the two shapes a template tree // is made of" rather than "is this one of the link kinds I have heard // of". diff --git a/internal/dast/engines/nuclei_test.go b/internal/dast/engines/nuclei_test.go index 299003f..a4e760b 100644 --- a/internal/dast/engines/nuclei_test.go +++ b/internal/dast/engines/nuclei_test.go @@ -5,8 +5,8 @@ // =========================================================================== // // Nuclei is NOT INSTALLED here (`Get-Command nuclei` finds nothing; `go list -// -m all` contains no projectdiscovery module — both measured on the -// development host, Windows 11, go1.26.5 windows/amd64). So no test here runs +// -m all` contains no projectdiscovery module — measured on the +// Windows 11 development machine, and again on 2026-10-03 on the Debian one). So no test here runs // the engine, and none pretends to: the engine is behind an interface, driven // by recorded shapes, and SystemEngine refuses on every host rather than // returning a no-op that would let a scan come back clean. @@ -33,7 +33,7 @@ // gate stack only ever shown to refuse could be refusing for the wrong reason, // so the positive control is what makes the negative ones mean anything. // -// internal/SKIPPED-CONTROLS.md entry U4 is the standing record of what is +// docs/controls.md entry U4 is the standing record of what is // still open: the engine, which is (a) and is about this host. // // =========================================================================== @@ -41,7 +41,7 @@ // =========================================================================== // // TestThisFileSkipsNothing reads this file's own syntax tree and fails if one -// appears. internal/SKIPPED-CONTROLS.md's opening section records two +// appears. docs/controls.md's opening section records two // separate occasions on which a skip retired a live security control behind a // green tick. package engines @@ -464,7 +464,7 @@ func TestTemplateDigestIsOverTheExactBytes(t *testing.T) { } } -// TestALinkInTheTemplateTreeIsRejected. internal/SKIPPED-CONTROLS.md H1 +// TestALinkInTheTemplateTreeIsRejected. docs/controls.md H1 // records this repository shipping a guard that a Windows DIRECTORY JUNCTION // walked straight through, because the test only ever created the privileged // kind of link. This one creates the strongest link the host permits and @@ -2415,7 +2415,7 @@ func TestScrubRemovesEveryHiddenClassAndCountsIt(t *testing.T) { // Structural guards over this package's own source // --------------------------------------------------------------------------- -// TestThisFileSkipsNothing. internal/SKIPPED-CONTROLS.md's opening section +// TestThisFileSkipsNothing. docs/controls.md's opening section // records two occasions on which a skip retired a live security control // behind a green tick. func TestThisFileSkipsNothing(t *testing.T) { diff --git a/internal/dast/engines/zap.go b/internal/dast/engines/zap.go index bcc5195..fe448c3 100644 --- a/internal/dast/engines/zap.go +++ b/internal/dast/engines/zap.go @@ -63,7 +63,7 @@ // - If ZAP is ever run WITHOUT the proxy, none of the above applies and // nothing in this file can make it apply. That is why the proxy is // required at construction rather than defaulted, and why -// internal/SKIPPED-CONTROLS.md U5 records that no run on this host has +// docs/controls.md U5 records that no run on this host has // ever demonstrated the proxied path end to end. // // # WHAT IS NOT ENFORCED — read this before relying on the hop bound @@ -170,7 +170,8 @@ // this host to run and no Docker to run `docker stats` against, so any figure // here would be fabricated — and a fabricated figure is worse than the // acknowledged gap, because tier-M sizing would then be documented against it. -// internal/SKIPPED-CONTROLS.md U5 records what measuring it takes. +// docs/controls.md U5 records what measuring it takes. + package engines import ( @@ -316,7 +317,7 @@ type ZapRunOutcome struct { // // Obligations 1 and 3 are STATED HERE AND ENFORCED NOWHERE IN THIS FILE. // They are contracts on the implementer, recorded in -// internal/SKIPPED-CONTROLS.md U5 as the specific things an integration lane +// docs/controls.md U5 as the specific things an integration lane // must prove. type ZapRunner interface { Autorun(ctx context.Context, inv ZapInvocation) (ZapRunOutcome, error) @@ -352,7 +353,7 @@ type ZapReportTemplate string // THESE TWO STRINGS ARE UNVERIFIED AGAINST AN INSTALLED ZAP. There is no ZAP // on this host to enumerate `zap.sh -cmd -autorun` report templates against, // so they are transcribed from the report add-on's documented template ids and -// recorded in internal/SKIPPED-CONTROLS.md U5 as a thing a real lane settles +// recorded in docs/controls.md U5 as a thing a real lane settles // in one command. What IS enforced here is that both are declared, both are // rendered into the plan, and a run that fails to write either one is a // refusal rather than a clean report. @@ -1655,7 +1656,7 @@ type ZapConfig struct { // // So there is no trigger check here and no field for one. WHERE THAT // SCHEDULED-ONLY RULE IS ENFORCED: nowhere in this package. That is stated -// rather than implied, and internal/SKIPPED-CONTROLS.md U5 records it as an +// rather than implied, and docs/controls.md U5 records it as an // unenforced contract with the name of the check that must exist elsewhere. type ZapDriver struct { mu sync.Mutex diff --git a/internal/dast/engines/zap_test.go b/internal/dast/engines/zap_test.go index bdc8a52..35111e1 100644 --- a/internal/dast/engines/zap_test.go +++ b/internal/dast/engines/zap_test.go @@ -5,7 +5,8 @@ // =========================================================================== // // ZAP is NOT INSTALLED here. MEASURED 2026-08-22 from PowerShell on the -// development host (Windows 11, go1.26.5 windows/amd64): +// Windows 11 development machine (re-measured 2026-10-03 on the Debian one: +// zap.sh, docker and java are all absent there): // // Get-Command zap.sh -> NOT FOUND // Get-Command zap -> NOT FOUND @@ -43,7 +44,7 @@ // and the tests that exercise the authorization boundary itself now use the // production route in both directions. // -// internal/SKIPPED-CONTROLS.md U5 records what remains unexecuted, including +// docs/controls.md U5 records what remains unexecuted, including // the one plan/design/dynamic-tier.md explicitly asked this step to answer — ZAP's JVM // memory footprint — which is NOT answered here, because there is no ZAP to // measure and a fabricated number would become tier-M sizing documentation. @@ -1798,7 +1799,7 @@ func TestNewZapDriverRefusesEveryUnauthorizedRoute(t *testing.T) { // The two tests here are the ones that could not be written: a ZapDriver built // by NewZapDriver from a real token, fired end to end, and the same driver // refusing a real kernel refusal without issuing anything. They are the ZAP -// counterparts of nuclei_test.go's items (2) and (3), and the SKIPPED-CONTROLS +// counterparts of nuclei_test.go's items (2) and (3), and the docs/controls.md // entry they close is U5(c), which recorded this blocker as shared with U4(b). // mustZapDriver assembles a ZapDriver against the real kernel: a real @@ -2166,7 +2167,7 @@ func TestTheAutomationPlanCarriesNoReferenceAWriterCouldReach(t *testing.T) { // reconcile with the ZAP driver's instruction rather than to discover it. // // WHERE THE SCHEDULED-ONLY RULE IS ENFORCED: nowhere in this package. -// internal/SKIPPED-CONTROLS.md U5 records it as an unenforced contract. +// docs/controls.md U5 records it as an unenforced contract. func TestThisDriverIsTriggerAgnosticByInstruction(t *testing.T) { v := reflect.TypeOf(ZapConfig{}) for i := 0; i < v.NumField(); i++ { @@ -2175,7 +2176,7 @@ func TestThisDriverIsTriggerAgnosticByInstruction(t *testing.T) { t.Fatalf("ZapConfig.%s exists. The ZAP driver's design requires this driver to be "+ "trigger-agnostic and puts the scheduled-only gate in the caller's "+ "trigger-policy check. If that instruction has changed, change "+ - "internal/SKIPPED-CONTROLS.md U5 in the same commit", v.Field(i).Name) + "docs/controls.md U5 in the same commit", v.Field(i).Name) } } f := reflect.TypeOf(ZapPlanFacts{}) @@ -2219,7 +2220,7 @@ func TestTheJVMFootprintIsNotFabricatedAnywhereInThisPackage(t *testing.T) { body := string(raw) if !strings.Contains(body, "unquantified") { t.Fatal("zap.go no longer records ZAP's JVM footprint as unquantified. If it was " + - "MEASURED, this test and internal/SKIPPED-CONTROLS.md U5 both change in the " + + "MEASURED, this test and docs/controls.md U5 both change in the " + "same commit as the measurement") } for _, f := range forbidden { @@ -2229,7 +2230,7 @@ func TestTheJVMFootprintIsNotFabricatedAnywhereInThisPackage(t *testing.T) { "ZAP and no Docker (both measured 2026-08-22, PowerShell). A figure here "+ "becomes tier-M sizing documentation. Settle it with a `docker stats` run "+ "during a representative scheduled scan and record the measurement in "+ - "internal/SKIPPED-CONTROLS.md U5", f) + "docs/controls.md U5", f) } } } diff --git a/internal/dast/inventory/auth_helper.go b/internal/dast/inventory/auth_helper.go index 3c8282a..6d53de5 100644 --- a/internal/dast/inventory/auth_helper.go +++ b/internal/dast/inventory/auth_helper.go @@ -131,7 +131,7 @@ // inertImports; adding it is a one-line edit in // internal/dast/authz/egress_chokepoint_test.go, is reported to the // orchestrator rather than made here, and is recorded as U10 in -// internal/SKIPPED-CONTROLS.md. +// docs/controls.md. // // a compressed artifact. compress/gzip IS allowlisted, so this one is // reachable; it is not done because an artifact sink that stores compressed @@ -214,6 +214,7 @@ // are stated on that interface and enforced nowhere here — the same shape // ClientSpider and engines.ZapRunner have, and the same integration lane owes // the proof. + package inventory import ( @@ -929,7 +930,7 @@ type AuthDriver interface { // Helper either. // // MEASURED 2026-08-22, PowerShell, on the development host (recorded in -// internal/dast/engines/zap.go's header and in SKIPPED-CONTROLS U5): no zap.sh, +// internal/dast/engines/zap.go's header and in docs/controls.md U5): no zap.sh, // no zap, no zap.bat, no docker. A JVM is present; ZAP is not. // // It never returns (nil, nil). @@ -2970,7 +2971,7 @@ func (s *Session) stepKind(oneBased int) AuthStepKind { // is NOT on gate 3's inertImports; adding it is a one-line edit in // internal/dast/authz/egress_chokepoint_test.go, is reported to the // orchestrator rather than made here, and is U10 in -// internal/SKIPPED-CONTROLS.md. It is the spelling the backstop's own +// docs/controls.md. It is the spelling the backstop's own // positive-control test uses. // // a compressed artifact, and A CREDENTIAL RENDERED AS PIXELS. Both are in diff --git a/internal/dast/inventory/auth_helper_test.go b/internal/dast/inventory/auth_helper_test.go index e89fa09..39f645f 100644 --- a/internal/dast/inventory/auth_helper_test.go +++ b/internal/dast/inventory/auth_helper_test.go @@ -1690,7 +1690,7 @@ func TestTheSweepIsABackstopAndTheProvenanceRuleIsTheControl(t *testing.T) { // import it — which is also why the encoder below is written out by hand // here. Adding the import is a one-line widening of the egress allowlist in // internal/dast/authz/egress_chokepoint_test.go, is reported to the - // orchestrator, and is recorded in internal/SKIPPED-CONTROLS.md (U10). + // orchestrator, and is recorded in docs/controls.md (U10). beyond := authBase64(authPassword) if _, hit := credentialIn([]byte(beyond), secrets); hit { t.Fatal("the sweep decoded base64, so this test no longer demonstrates the " + diff --git a/internal/dast/inventory/doc.go b/internal/dast/inventory/doc.go new file mode 100644 index 0000000..65af06d --- /dev/null +++ b/internal/dast/inventory/doc.go @@ -0,0 +1,4 @@ +// Package inventory builds Anvil's attack-surface inventory: the list of +// endpoints the DAST tier believes a target exposes, and — for every one of +// them — WHERE THAT BELIEF CAME FROM and WHETHER ANVIL CONFIRMED IT. +package inventory diff --git a/internal/dast/inventory/tier0_runtime.go b/internal/dast/inventory/tier0_runtime.go index 0f52cbe..1654ff0 100644 --- a/internal/dast/inventory/tier0_runtime.go +++ b/internal/dast/inventory/tier0_runtime.go @@ -1,7 +1,3 @@ -// Package inventory builds Anvil's attack-surface inventory: the list of -// endpoints the DAST tier believes a target exposes, and — for every one of -// them — WHERE THAT BELIEF CAME FROM and WHETHER ANVIL CONFIRMED IT. -// // This file is the runtime spec probe: Tier 0, the runtime spec route. // // =========================================================================== @@ -103,7 +99,7 @@ // authz.Adjudicate is the only mint for an authz.Authorization, the admission // chain contains Gate11RobotsDeny, and nothing is registered for it — so the // chain refuses every target there and NO Authorization can be constructed -// from outside package authz. internal/SKIPPED-CONTROLS.md U4 records this. +// from outside package authz. docs/controls.md U4 records this. // The consequence for the runtime spec probe is exact: Probe's admit-and-fetch path cannot reach // a fetcher today, and every test of it asserts a refusal. // @@ -152,6 +148,7 @@ // Contract (lines 1142-1160); research/22-attack-surface-discovery.md lines // 319-323; internal/record/contract.go (InventoryProvenance, Trust, // DastCoverage). + package inventory import ( @@ -229,7 +226,7 @@ const ( // // # Why this is declared here and not in internal/record // -// internal/record owns every shared enum (its package doc: "The record area owns every +// internal/record owns every shared enum (contract.go's header comment: "The record area owns every // shared enum, because it owns the record contract, and no other area may // declare one"). record.InventoryProvenance is there for exactly that reason. // The confirmed/candidate axis is NOT — record carries it as two aggregate @@ -1096,7 +1093,7 @@ func (p ParseResult) AssertAccountedFor() error { // // It is a PURE function of (target, servedAt, contentType, body) and it opens // nothing. That is what makes Tier 0's extraction fully testable while gate 11 -// keeps the fetch half unreachable (internal/SKIPPED-CONTROLS.md U4): a +// keeps the fetch half unreachable (docs/controls.md U4): a // kernel Target is authz.NewTarget's output and needs no authorization, // because it is gate 8/9's product and not a permission. // diff --git a/internal/dast/inventory/tier0_runtime_test.go b/internal/dast/inventory/tier0_runtime_test.go index 5e609ee..daf4ad2 100644 --- a/internal/dast/inventory/tier0_runtime_test.go +++ b/internal/dast/inventory/tier0_runtime_test.go @@ -22,7 +22,7 @@ // authz.Adjudicate is the only mint for an authz.Authorization, the admission // chain contains Gate11RobotsDeny, and nothing is registered for it — so no // Authorization can be constructed from outside package authz at all -// (internal/SKIPPED-CONTROLS.md U4). Every test below that would need one +// (docs/controls.md U4). Every test below that would need one // asserts a refusal instead, and // TestNoAuthorizationCanBeMintedUntilGate11IsRegistered FAILS on the day that // changes, listing what must then be written. There is no t.Skip in this file. @@ -1854,7 +1854,7 @@ func TestTheHardCodedPathScannerCanSeeAViolation(t *testing.T) { // =========================================================================== // THE PROBE PATH, END TO END // -// internal/SKIPPED-CONTROLS.md U4 records that no authz.Authorization could be +// docs/controls.md U4 records that no authz.Authorization could be // minted from outside package authz, because Gate11RobotsDeny sat in the // admission chain with no implementation. That is NO LONGER TRUE in this tree: // gate 11 has moved to the Governor's per-request chain @@ -1892,7 +1892,7 @@ func mintAuthorization(t *testing.T) (authz.Authorization, *countingSink) { Tier 0 cannot issue a spec fetch without an authz.Authorization, and Adjudicate is the only mint. If gate 11 has been put back into kernel.go's admissionChain, this whole file's fetch half becomes unreachable again and -internal/SKIPPED-CONTROLS.md U4 needs reopening for Tier 0 as well as for the nuclei driver.`, +docs/controls.md U4 needs reopening for Tier 0 as well as for the nuclei driver.`, dec.Gate(), dec.Reason(), dec.Err()) } auth, err := dec.Authorization() @@ -2723,7 +2723,7 @@ func TestJSONNumbersAndBooleansInPlaceOfStringsDoNotBecomeRoutes(t *testing.T) { // TestThisPackageSkipsNothing. // -// internal/SKIPPED-CONTROLS.md exists because "a guard that vanishes silently +// docs/controls.md exists because "a guard that vanishes silently // when it cannot run is worse than no guard, because the green tick is read as // an answer" — and it happened twice in this repository before anyone noticed. // U6's entry claims this package has zero skip sites; this is what makes the @@ -2775,7 +2775,7 @@ func TestThisPackageSkipsNothing(t *testing.T) { } t.Errorf(`%s calls %s. -internal/SKIPPED-CONTROLS.md U6 records that this package has ZERO skip sites, +docs/controls.md U6 records that this package has ZERO skip sites, and a skip here would let the package print "ok" while the control it guards went unrun. If the skip is genuinely platform-specific, it has to be listed in that document with what would settle it — and this test updated in the same diff --git a/internal/dast/inventory/tier1_repospec.go b/internal/dast/inventory/tier1_repospec.go index 3344d65..8a16f3b 100644 --- a/internal/dast/inventory/tier1_repospec.go +++ b/internal/dast/inventory/tier1_repospec.go @@ -145,6 +145,7 @@ // Contract (lines 1142-1160); research/22-attack-surface-discovery.md lines // 325-328; internal/record/contract.go (InventoryProvenance, Trust, // ArtifactLocation, ArtifactContent, DastCoverage). + package inventory import ( @@ -918,7 +919,7 @@ func retagAll(target authz.Target, pr ParseResult) ParseResult { // and cannot: Tier 1 issues no request at all, which is why this whole packet // is exercisable today while the runtime spec probe's fetch half depends on the kernel // admitting. There is no t.Skip anywhere in this tier and nothing in -// internal/SKIPPED-CONTROLS.md belongs to it. +// docs/controls.md belongs to it. func IngestSpecFile(cfg IngestConfig, f SpecFile) (FileResult, error) { if !cfg.Target.Constructed() { return FileResult{}, fmt.Errorf("inventory: %w: IngestSpecFile was handed a Target "+ diff --git a/internal/dast/inventory/tier1_repospec_test.go b/internal/dast/inventory/tier1_repospec_test.go index 3b3494a..293b234 100644 --- a/internal/dast/inventory/tier1_repospec_test.go +++ b/internal/dast/inventory/tier1_repospec_test.go @@ -6,7 +6,7 @@ // // EVERYTHING IN THIS PACKET IS PROVEN END TO END. Tier 1 issues no request, so // none of it depends on the kernel admitting anything, none of it needs a -// SpecFetcher, and nothing in it belongs in internal/SKIPPED-CONTROLS.md. +// SpecFetcher, and nothing in it belongs in docs/controls.md. // There is no t.Skip in this file. // // all five input formats the packet names parse to Route lists — OpenAPI @@ -1955,7 +1955,7 @@ func TestTheTier1StructuralScannersCanSeeAViolation(t *testing.T) { } // TestTier1ContainsNoSkip. The packet's standing rule is that an unprovable -// control is written down in internal/SKIPPED-CONTROLS.md, never turned into a +// control is written down in docs/controls.md, never turned into a // t.Skip. Tier 1 issues no request and has nothing unprovable in it, so the // rule reduces to a scan. func TestTier1ContainsNoSkip(t *testing.T) { @@ -1973,7 +1973,7 @@ func TestTier1ContainsNoSkip(t *testing.T) { switch sel.Sel.Name { case "Skip", "Skipf", "SkipNow": t.Errorf("%s calls t.%s at %s. An unprovable control is written down in "+ - "internal/SKIPPED-CONTROLS.md naming what would settle it, never "+ + "docs/controls.md naming what would settle it, never "+ "skipped", file, sel.Sel.Name, fset.Position(sel.Pos())) } return true diff --git a/internal/dast/inventory/tier2_confirm.go b/internal/dast/inventory/tier2_confirm.go index 9399bef..ea098b8 100644 --- a/internal/dast/inventory/tier2_confirm.go +++ b/internal/dast/inventory/tier2_confirm.go @@ -67,6 +67,7 @@ // ("promote to confirmed only on a non-404 response") and Risk #4 ("more // endpoints can mean less coverage" — the phpBB regression from timeout // exhaustion), which is what ProbeBudget and AssertBudgetSufficed exist for. + package inventory import ( diff --git a/internal/dast/inventory/tier2_go_extract.go b/internal/dast/inventory/tier2_go_extract.go index 46187b8..b3d6391 100644 --- a/internal/dast/inventory/tier2_go_extract.go +++ b/internal/dast/inventory/tier2_go_extract.go @@ -200,6 +200,7 @@ // Sources: Go route extraction's design (lines 644-676) and the Coverage Reporting // Contract (lines 1142-1160); research/22-attack-surface-discovery.md lines // 330-341; third_party/go-apispec/PIN.md; internal/record/contract.go. + package inventory import ( @@ -701,7 +702,7 @@ type ExtractedRoute struct { // with network and toolchain reach that belongs behind network containment. // // Nothing implements this today. third_party/go-apispec/PIN.md section 5 -// records the two measured blockers. internal/SKIPPED-CONTROLS.md is where the +// records the two measured blockers. docs/controls.md is where the // unprovable half is named; there is no t.Skip in this package. type TypeCheckedExtractor interface { // ExtractTypeChecked returns one ExtractedRoute per registration the diff --git a/internal/dast/inventory/tier2_go_extract_test.go b/internal/dast/inventory/tier2_go_extract_test.go index 3acb9f7..53b06f9 100644 --- a/internal/dast/inventory/tier2_go_extract_test.go +++ b/internal/dast/inventory/tier2_go_extract_test.go @@ -1489,7 +1489,7 @@ const ( // repoRootForLicenceGuard walks up to the module root, and FAILS rather than // skipping when it cannot find it. // -// internal/SKIPPED-CONTROLS.md records this repository shipping two guards that +// docs/controls.md records this repository shipping two guards that // vanished silently when they could not run. A compliance check that cannot // locate the tree it guards has not passed; it has not run. func repoRootForLicenceGuard(t *testing.T) string { diff --git a/internal/dast/inventory/tier2_other_extract.go b/internal/dast/inventory/tier2_other_extract.go index f312d24..e3a6b60 100644 --- a/internal/dast/inventory/tier2_other_extract.go +++ b/internal/dast/inventory/tier2_other_extract.go @@ -112,6 +112,7 @@ // Sources: non-Go route extraction's design (lines 677-708) and the Coverage Reporting // Contract (lines 1142-1160); research/22-attack-surface-discovery.md lines // 330-341 and its Risk #2; internal/dast/inventory/tier2_go_extract.go (Go route extraction). + package inventory import ( diff --git a/internal/dast/inventory/tier3_crawl.go b/internal/dast/inventory/tier3_crawl.go index 6c85207..bd62bda 100644 --- a/internal/dast/inventory/tier3_crawl.go +++ b/internal/dast/inventory/tier3_crawl.go @@ -111,6 +111,7 @@ // exclusion list bounds nothing but waste. So the list failing open costs // coverage, never containment, and every miss is a recorded visit an operator // can read back. + package inventory import ( @@ -741,7 +742,7 @@ type CrawlPage struct { // implementation declares which of these it is, and validateCrawlConfig // refuses anything that is not FetchDisciplineSingleRequest. A refusal Anvil // makes before the first request beats an obligation no implementation can -// keep, and the gap is recorded in internal/SKIPPED-CONTROLS.md rather than +// keep, and the gap is recorded in docs/controls.md rather than // left as a control that exists only in prose. type FetchDiscipline string @@ -801,7 +802,7 @@ func (d FetchDiscipline) PermitsCrawl() bool { return admittedDisciplines()[d] } // pass Discipline() and Anvil refuses to drive it. That is not a decision // against ZAP; it is the honest consequence of obligation 1 below. What would // settle it is a seam that hands each sub-request back for admission, and it -// is written up in internal/SKIPPED-CONTROLS.md rather than implied by a +// is written up in docs/controls.md rather than implied by a // promise nobody can keep. // // # The obligations an implementation takes on @@ -845,7 +846,7 @@ type ClientSpider interface { // silent-clean failure this whole tier is written against. // // MEASURED 2026-08-22, PowerShell, on the development host (recorded in -// internal/dast/engines/zap.go's header and in SKIPPED-CONTROLS U5): no zap.sh, +// internal/dast/engines/zap.go's header and in docs/controls.md U5): no zap.sh, // no zap, no zap.bat, no docker. A JVM is present; ZAP is not. // // It never returns (nil, nil). @@ -1362,7 +1363,7 @@ func checkFetchDiscipline(d FetchDiscipline) error { "kernel and not a style question. CrawlPage has no field in which the seam "+ "could report them, so Anvil cannot admit them after the fact either. This "+ "is refused rather than driven, and the gap is recorded in "+ - "internal/SKIPPED-CONTROLS.md with what would close it", + "docs/controls.md with what would close it", ErrRefused, string(d)) } return fmt.Errorf("inventory: %w: the wired ClientSpider declares fetch discipline %q, "+ @@ -1493,7 +1494,7 @@ func (s *crawlState) crawlOne(ctx context.Context, item frontierItem, now authz. // would mean resolving an attacker-chosen name — which is the DNS lookup // gate 9 exists to bound, performed on behalf of the party the crawl is // pointed at. Refusing the link without resolving it is the cheaper and - // safer order. internal/SKIPPED-CONTROLS.md records what would change it. + // safer order. docs/controls.md records what would change it. intent, err := authz.NewRequestIntent(authz.RequestFacts{ Origin: item.origin, Admitted: s.cfg.Target, diff --git a/internal/dast/inventory/tier3_crawl_test.go b/internal/dast/inventory/tier3_crawl_test.go index a9ce575..c5cbadd 100644 --- a/internal/dast/inventory/tier3_crawl_test.go +++ b/internal/dast/inventory/tier3_crawl_test.go @@ -2151,7 +2151,7 @@ func TestNoCrawlRequestCanCarryASession(t *testing.T) { // // Obligation 1 was therefore a contract THE CHOSEN IMPLEMENTATION CANNOT KEEP. // This is the refusal that replaces it. The gap is recorded in -// internal/SKIPPED-CONTROLS.md with what would close it. +// docs/controls.md with what would close it. func TestARenderingSpiderIsRefusedRatherThanDriven(t *testing.T) { cases := []struct { name string @@ -2252,7 +2252,7 @@ func TestTheDisciplineAllowlistIsAnAllowlist(t *testing.T) { // The refusal must NAME the sub-request problem rather than saying "bad // value": an operator who wired a browser needs to know why. err := checkFetchDiscipline(FetchDisciplineRendering) - for _, want := range []string{"SUB-REQUESTS", "gate-14", "gate-21", "SKIPPED-CONTROLS"} { + for _, want := range []string{"SUB-REQUESTS", "gate-14", "gate-21", "docs/controls.md"} { if !strings.Contains(err.Error(), want) { t.Fatalf("the rendering refusal does not mention %q: %v", want, err) } diff --git a/internal/dast/record/confirm_gate.go b/internal/dast/record/confirm_gate.go index 7e87ba7..dc7285f 100644 --- a/internal/dast/record/confirm_gate.go +++ b/internal/dast/record/confirm_gate.go @@ -256,6 +256,7 @@ // string into a tree and a tree into a program. Every package here that imports // `regexp` already links it transitively; gate 3 attributes by import line // rather than transitively, which is why the line was needed at all. + package record import ( diff --git a/internal/dast/record/coverage.go b/internal/dast/record/coverage.go index 4ea3a90..6d038e2 100644 --- a/internal/dast/record/coverage.go +++ b/internal/dast/record/coverage.go @@ -1,10 +1,3 @@ -// Package record aggregates the dynamic tier's per-tier inventory facts into the -// record-level coverage fields internal/record freezes: `dast_coverage` -// (record.DastCoverage), `endpoint_coverage`, `server_line_coverage` and -// `inventory_provenance`, plus the two target fields `target_provenance` -// (record.TargetProvenance) and `target.provisioning` -// (record.TargetProvisioning). -// // This is coverage reporting (plan node coverage). It produces THE NUMBER AN OPERATOR TRUSTS, so // every design decision below is about the number looking better than the // evidence behind it. @@ -107,6 +100,7 @@ // (DastCoverage, InventoryProvenance, TargetProvenance, TargetProvisioning); // research/22-attack-surface-discovery.md lines 364-374 and Risk #4; // research/23-dast-signal-sources.md Risk #1. + package record import ( @@ -314,7 +308,7 @@ const ( QualifierTierFactsAbsent QualifierReason = "tier_facts_absent" // QualifierOperationsCollapsed: more operations than addresses. See the - // package doc, decision 4. + // comment at the top of this file, decision 4. QualifierOperationsCollapsed QualifierReason = "operations_collapsed_onto_addresses" // QualifierProbeBudgetExhausted: endpoints in the union were never @@ -499,7 +493,7 @@ type ProvenanceRow struct { // Operations are the operation names this endpoint carries, sorted. It // is non-empty mainly for GraphQL, where many operations share one - // address; see the package doc, decision 4. + // address; see the comment at the top of this file, decision 4. Operations []string } @@ -735,7 +729,7 @@ func (s Summary) CandidateCount() int { return s.InventoryUnionCount() - s.confi // OperationCount is route confirmation's second number: distinct (endpoint, operation) // pairs. Reported BESIDE the endpoint count, never substituted into the -// denominator. See the package doc, decision 4. +// denominator. See the comment at the top of this file, decision 4. func (s Summary) OperationCount() int { return s.operations } // RequestsIssued is how many requests actually left through the seam. diff --git a/internal/dast/record/doc.go b/internal/dast/record/doc.go new file mode 100644 index 0000000..303ceee --- /dev/null +++ b/internal/dast/record/doc.go @@ -0,0 +1,7 @@ +// Package record aggregates the dynamic tier's per-tier inventory facts into the +// record-level coverage fields internal/record freezes: `dast_coverage` +// (record.DastCoverage), `endpoint_coverage`, `server_line_coverage` and +// `inventory_provenance`, plus the two target fields `target_provenance` +// (record.TargetProvenance) and `target.provisioning` +// (record.TargetProvisioning). +package record diff --git a/internal/dast/target/doc.go b/internal/dast/target/doc.go new file mode 100644 index 0000000..1b5cd83 --- /dev/null +++ b/internal/dast/target/doc.go @@ -0,0 +1,4 @@ +// Package target parses `.anvil/target.yaml`, the DECLARED description of a +// repository's runtime target, and decides -- once, in one place -- whether the +// DAST half has a target to work with at all. +package target diff --git a/internal/dast/target/manifest.go b/internal/dast/target/manifest.go index 1237443..c973506 100644 --- a/internal/dast/target/manifest.go +++ b/internal/dast/target/manifest.go @@ -1,7 +1,3 @@ -// Package target parses `.anvil/target.yaml`, the DECLARED description of a -// repository's runtime target, and decides -- once, in one place -- whether the -// DAST half has a target to work with at all. -// // The one sentence that governs every decision in this file, from // plan/design/dynamic-tier.md's "Target Manifest Schema" section: // @@ -49,6 +45,7 @@ // scalars, multi-document streams, flow mappings, nested collections inside // sequences, tabs). Refusing an unsupported construct is safe; guessing at one // is not. + package target import ( diff --git a/internal/handoff/claim.go b/internal/handoff/claim.go index 37078a0..efe8019 100644 --- a/internal/handoff/claim.go +++ b/internal/handoff/claim.go @@ -801,8 +801,8 @@ func (q *Queue) packetGate(ctx context.Context, h Handle) error { // data=writeback. This code does not depend on that heuristic. // // Windows has no fsync-able directory handle, so the parent fsync is skipped -// there and only there. Anvil's packet lives on tmpfs on Linux; Windows is a -// development host. +// there and only there. Anvil's packet lives on tmpfs on Linux; Windows is +// only ever a build and test platform. func (q *Queue) WritePacket(h Handle, data []byte) (string, error) { return q.WritePacketContext(context.Background(), h, data) } diff --git a/internal/handoff/doc.go b/internal/handoff/doc.go new file mode 100644 index 0000000..d8c8afe --- /dev/null +++ b/internal/handoff/doc.go @@ -0,0 +1,3 @@ +// Package handoff implements the claim/lease protocol for the single +// `handoff` table defined by internal/store/schema.sql. +package handoff diff --git a/internal/handoff/reaper.go b/internal/handoff/reaper.go index ca080e9..bdb5b9a 100644 --- a/internal/handoff/reaper.go +++ b/internal/handoff/reaper.go @@ -410,7 +410,7 @@ func (q *Queue) ReapContext(ctx context.Context) (ReapReport, error) { // // Found by CI, not locally: the race detector slows execution enough to widen // the cancel-during-sweep window from rare to reliable. It reproduced on -// ubuntu-latest under -race while passing every run on the Windows dev host, +// ubuntu-latest under -race while passing every run on the Windows machine development then used, // which is precisely why -race is a required check rather than an optional one. func (q *Queue) Run(ctx context.Context, interval time.Duration, observe func(ReapReport, error)) error { if interval <= 0 { diff --git a/internal/handoff/state_machine.go b/internal/handoff/state_machine.go index 50e4fdc..3d1c042 100644 --- a/internal/handoff/state_machine.go +++ b/internal/handoff/state_machine.go @@ -1,6 +1,3 @@ -// Package handoff implements the claim/lease protocol for the single -// `handoff` table defined by internal/store/schema.sql. -// // WHAT THIS PACKAGE IS, AND WHY THERE IS ONLY ONE OF IT. // The handoff-table ruling: "The record area owns the table and the // claim/lease protocol." The handoff adapter no longer writes a migration and no longer @@ -73,6 +70,7 @@ // The tmpfs packet is still written by research/08 §C's durable recipe // (exclusive temp file in the same directory, fsync, rename, fsync parent) and // never relies on ext4's auto_da_alloc; see WritePacket. + package handoff import ( diff --git a/internal/ingest/bootstrap/bootstrap.go b/internal/ingest/bootstrap/bootstrap.go index a0cac23..feec18e 100644 --- a/internal/ingest/bootstrap/bootstrap.go +++ b/internal/ingest/bootstrap/bootstrap.go @@ -1,6 +1,3 @@ -// Package bootstrap fills a feed's ingestion cache ONCE, from a bulk artifact, -// so that the conditional-GET poller only ever has to carry deltas. -// // This is the bulk bootstrap (plan node bootstrap). Lane A is the // zero-inference half of Anvil (the spine's corrected-requirements table): CVE/OSV/GHSA describe // vulnerable PACKAGE VERSIONS and a version comparator answers that exactly and @@ -124,6 +121,7 @@ // - It does not name a feed. There is no feed id, URL, cadence or format // mapping compiled into this file. Which feeds exist comes from the feed // table; what an artifact CONTAINS is decided by looking at the bytes. + package bootstrap import ( diff --git a/internal/ingest/bootstrap/doc.go b/internal/ingest/bootstrap/doc.go new file mode 100644 index 0000000..f583d24 --- /dev/null +++ b/internal/ingest/bootstrap/doc.go @@ -0,0 +1,3 @@ +// Package bootstrap fills a feed's ingestion cache ONCE, from a bulk artifact, +// so that the conditional-GET poller only ever has to carry deltas. +package bootstrap diff --git a/internal/ingest/cache/cache_test.go b/internal/ingest/cache/cache_test.go index fde4f9c..01e76f9 100644 --- a/internal/ingest/cache/cache_test.go +++ b/internal/ingest/cache/cache_test.go @@ -969,7 +969,7 @@ func TestFeedStateAcceptsEveryFeedIDInTheShippedConfig(t *testing.T) { // skip here reports "the produce/consume edge between the feed table // and the ingestion cache was verified" whenever the file moves, is // renamed, or stops parsing -- which is the exact failure mode - // internal/SKIPPED-CONTROLS.md is about. + // docs/controls.md is about. t.Fatalf("The feed table's example config could not be loaded, so the feed_id domain shared by the feed table "+ "and the ingestion cache was NOT cross-checked: %v", err) } diff --git a/internal/ingest/cache/doc.go b/internal/ingest/cache/doc.go new file mode 100644 index 0000000..8e1fde3 --- /dev/null +++ b/internal/ingest/cache/doc.go @@ -0,0 +1,4 @@ +// Package cache owns the Lane A ingestion cache: a SECOND SQLite file, +// `anvil-cache.sqlite`, holding advisory feed content (plan node cache; design in +// plan/design/lane-a.md). +package cache diff --git a/internal/ingest/cache/schema.go b/internal/ingest/cache/schema.go index ec3a62a..f8ff4b3 100644 --- a/internal/ingest/cache/schema.go +++ b/internal/ingest/cache/schema.go @@ -1,7 +1,3 @@ -// Package cache owns the Lane A ingestion cache: a SECOND SQLite file, -// `anvil-cache.sqlite`, holding advisory feed content (plan node cache; design in -// plan/design/lane-a.md). -// // # This is not the store of record // // internal/store is Anvil's audit store of record and it is a frozen @@ -56,6 +52,7 @@ // directories by reading checked-in LICENSE file bodies. This schema only // RECORDS the outcome, and refuses a row that records nothing at all. // - It does not fetch. The poller polls and the bulk bootstrap bootstraps. + package cache import ( diff --git a/internal/ingest/config/doc.go b/internal/ingest/config/doc.go new file mode 100644 index 0000000..c1a82de --- /dev/null +++ b/internal/ingest/config/doc.go @@ -0,0 +1,2 @@ +// Package config loads Anvil's advisory-feed table from DATA. +package config diff --git a/internal/ingest/config/feeds.go b/internal/ingest/config/feeds.go index 3f36460..e205242 100644 --- a/internal/ingest/config/feeds.go +++ b/internal/ingest/config/feeds.go @@ -1,5 +1,3 @@ -// Package config loads Anvil's advisory-feed table from DATA. -// // This is the feed table (plan node feeds). Lane A is the // zero-inference half of Anvil (the spine's corrected-requirements table): a tiered conditional-GET // poller filling one SQLite+FTS5 cache, plus two collectors feeding a version @@ -59,6 +57,7 @@ // and no SPDX identifier, and that "attribution is requested" is a request, // not a grant. Such a feed is legal here only at LicenseTier3 — optional, // opt-in, risk-accepted — and Anvil must never describe it as open licensed. + package config import ( diff --git a/internal/ingest/decode/decode.go b/internal/ingest/decode/decode.go index e757df2..408ee4a 100644 --- a/internal/ingest/decode/decode.go +++ b/internal/ingest/decode/decode.go @@ -1,7 +1,3 @@ -// Package decode turns a publisher's advisory document into the row shape -// internal/ingest/cache stores. It is the ONE implementation of every wire -// format Lane A reads. -// // =========================================================================== // WHY THIS PACKAGE EXISTS — THE ONE-DECODER RULING // =========================================================================== @@ -67,6 +63,7 @@ // The callers re-prove it at the write site with sanitize.AssertAllSanitized // on the exact values about to be bound. This package's guarantee is not // trusted there; it is checked. + package decode import ( diff --git a/internal/ingest/decode/doc.go b/internal/ingest/decode/doc.go new file mode 100644 index 0000000..9a30848 --- /dev/null +++ b/internal/ingest/decode/doc.go @@ -0,0 +1,4 @@ +// Package decode turns a publisher's advisory document into the row shape +// internal/ingest/cache stores. It is the ONE implementation of every wire +// format Lane A reads. +package decode diff --git a/internal/ingest/delta/delta.go b/internal/ingest/delta/delta.go index 309d3b9..afe8d58 100644 --- a/internal/ingest/delta/delta.go +++ b/internal/ingest/delta/delta.go @@ -1,5 +1,3 @@ -// Package delta is Lane A's steady-state delta ingestion. -// // =========================================================================== // WHAT THIS PACKAGE IS FOR // =========================================================================== @@ -103,6 +101,7 @@ // // A refusal is loud, typed and counted. It is not a silent no-op, and it is // not a t.Skip. + package delta import ( @@ -159,7 +158,7 @@ var ( ErrNoDeltaLog = fmt.Errorf("%w: feed has no delta log", ErrSyncRefused) // ErrNoReconciler is RouteReconcile with nothing wired to run it. See the - // package comment: defaulting it to the bulk bootstrap's bulk importer would cost 570 MB + // header comment: defaulting it to the bulk bootstrap's bulk importer would cost 570 MB // a day. ErrNoReconciler = fmt.Errorf("%w: no reconciler", ErrSyncRefused) @@ -241,7 +240,7 @@ const ( RouteFeedBody Route = "feed_body" // RouteReconcile is the periodic wider-window pass on - // reconcile_interval_seconds. See the package comment for why it is + // reconcile_interval_seconds. See the comment at the top of this file for why it is // planned here and refused unless delegated. RouteReconcile Route = "reconcile" @@ -307,7 +306,7 @@ type Plan struct { LastOK time.Time // ReconcileDue and BaselineDue are the two wider passes. They are - // reported rather than run; see the package comment. + // reported rather than run; see the comment at the top of this file. // // Both are WINDOW-BOUNDARY tests, not elapsed-time tests: reconcile is due // when `now` and `LastOK` fall in different reconcile windows. For the @@ -596,7 +595,7 @@ type Source interface { Record(ctx context.Context, feed config.FeedConfig, id string) ([]byte, error) } -// Reconciler runs RouteReconcile. See the package comment for why this package +// Reconciler runs RouteReconcile. See the comment at the top of this file for why this package // refuses rather than defaulting the route to the bulk bootstrap's bulk importer. type Reconciler interface { Reconcile(ctx context.Context, feed config.FeedConfig) (BatchStats, error) diff --git a/internal/ingest/delta/doc.go b/internal/ingest/delta/doc.go new file mode 100644 index 0000000..fc52483 --- /dev/null +++ b/internal/ingest/delta/doc.go @@ -0,0 +1,2 @@ +// Package delta is Lane A's steady-state delta ingestion. +package delta diff --git a/internal/ingest/delta/upsert.go b/internal/ingest/delta/upsert.go index 1ff73bb..427be9d 100644 --- a/internal/ingest/delta/upsert.go +++ b/internal/ingest/delta/upsert.go @@ -9,7 +9,7 @@ // CREATE, no DELETE FROM advisory_fts that is not scoped to a single rowid, no // `INSERT INTO advisory_fts(advisory_fts) VALUES('rebuild')`. // -// internal/ingest/cache's own package comment gives the reason: "FTS5 accepts +// the comment at the top of internal/ingest/cache/schema.go gives the reason: "FTS5 accepts // incremental INSERT/DELETE, so an hourly delta touching 200 records costs 200 // row upserts and NOT a rebuild. That is why no code path in Anvil may DROP or // rebuild `advisory_fts`." delta ingestion's design repeats it as a forbidden action, and @@ -55,6 +55,7 @@ // call. internal/ingest/sanitize's writer guard sees the assertion in the same // function as the bind, which is what it can check; the assertion is what makes // the claim true rather than merely visible. + package delta import ( diff --git a/internal/ingest/drift/doc.go b/internal/ingest/drift/doc.go new file mode 100644 index 0000000..7feea57 --- /dev/null +++ b/internal/ingest/drift/doc.go @@ -0,0 +1,3 @@ +// Package drift is drift handling: what Anvil does when a feed changes shape underneath +// it, and what it does when a publisher takes an advisory back. +package drift diff --git a/internal/ingest/drift/drift.go b/internal/ingest/drift/drift.go index a479b35..97facf5 100644 --- a/internal/ingest/drift/drift.go +++ b/internal/ingest/drift/drift.go @@ -1,6 +1,3 @@ -// Package drift is drift handling: what Anvil does when a feed changes shape underneath -// it, and what it does when a publisher takes an advisory back. -// // =========================================================================== // THE ONE RULE THIS PACKAGE EXISTS TO KEEP // =========================================================================== @@ -27,7 +24,7 @@ // =========================================================================== // // It does NOT mean a second CVE decoder. internal/ingest/delta already has one -// and internal/ingest/bootstrap has another, and delta's own package comment +// and internal/ingest/bootstrap has another, and the comment at the top of delta.go // records that the second one is a real cross-area hazard which its // conformance test exists to contain. A THIRD would be the same defect with a // third name on it, so this package extracts NOTHING itself: it decides which @@ -83,6 +80,7 @@ // cache on the tombstone path). // - It does not compute a fingerprint, derive one, or compare against one. // anvil-fp/v1 is internal/record's and is the only one (the spine's record section). + package drift import ( @@ -623,7 +621,7 @@ func decodeOne(feedID string, raw []byte) (Record, error) { // primary key and the publisher's bytes. // // It is deliberately minimal. Reconstructing severity or version ranges here -// would be the third decoder the package comment refuses to write, and a +// would be the third decoder the comment at the top of this file refuses to write, and a // half-reconstructed record is exactly the "looks complete and is not" outcome // drift handling exists to prevent. What survives is enough for the record to be found // again and re-parsed by a later build that understands the shape. diff --git a/internal/ingest/drift/tombstone.go b/internal/ingest/drift/tombstone.go index 8d3ac3d..476fd5b 100644 --- a/internal/ingest/drift/tombstone.go +++ b/internal/ingest/drift/tombstone.go @@ -52,6 +52,7 @@ // INSERT OR REPLACE would delete and re-insert under a NEW rowid and // orphan the FTS entry silently — the exact defect cache/schema.go // documents at the `advisory` table. + package drift import ( diff --git a/internal/ingest/invisible/doc.go b/internal/ingest/invisible/doc.go new file mode 100644 index 0000000..09346bf --- /dev/null +++ b/internal/ingest/invisible/doc.go @@ -0,0 +1,2 @@ +// Package invisible is THE definition of "renders as nothing" for Lane A. +package invisible diff --git a/internal/ingest/invisible/invisible.go b/internal/ingest/invisible/invisible.go index e67da68..fdefde2 100644 --- a/internal/ingest/invisible/invisible.go +++ b/internal/ingest/invisible/invisible.go @@ -1,5 +1,3 @@ -// Package invisible is THE definition of "renders as nothing" for Lane A. -// // =========================================================================== // WHY THIS PACKAGE EXISTS AT ALL // =========================================================================== @@ -94,6 +92,7 @@ // that a renderer must draw nothing for them, which is the class's question // answered from a table. An unassigned code point with no such property draws a // .notdef box, which is visible residue, and stays out. + package invisible import "unicode" diff --git a/internal/ingest/invisible/invisible_test.go b/internal/ingest/invisible/invisible_test.go index f833ce6..17f7655 100644 --- a/internal/ingest/invisible/invisible_test.go +++ b/internal/ingest/invisible/invisible_test.go @@ -575,7 +575,7 @@ func TestTheReservedDefaultIgnorablesAreInTheClass(t *testing.T) { // split a share-alike marker -- would come back invisibly. t.Fatal("this toolchain does not ship Other_Default_Ignorable_Code_Point, so the reserved " + "half of the invisible class was NOT checked. This fails rather than skips: " + - "see internal/SKIPPED-CONTROLS.md.") + "see docs/controls.md.") } reserved := 0 for r := rune(0); r <= unicode.MaxRune; r++ { diff --git a/internal/ingest/license/doc.go b/internal/ingest/license/doc.go new file mode 100644 index 0000000..4310d59 --- /dev/null +++ b/internal/ingest/license/doc.go @@ -0,0 +1,3 @@ +// Package license is Lane A's licence gate and the segregated mirror layout it +// enforces. This is the licence gate (plan node licencegate). +package license diff --git a/internal/ingest/license/tiers.go b/internal/ingest/license/tiers.go index 3289d21..9563753 100644 --- a/internal/ingest/license/tiers.go +++ b/internal/ingest/license/tiers.go @@ -1,6 +1,3 @@ -// Package license is Lane A's licence gate and the segregated mirror layout it -// enforces. This is the licence gate (plan node licencegate). -// // # NO FEED IS ADMITTED BY A FRESH CLONE. THAT IS THE DESIGN. // // Clone this repository, run the gate, and every feed is refused. Nothing is @@ -149,6 +146,7 @@ // are never compared against a canonical fingerprint. // - It does not carry CIS Benchmark content, or text derived from reading one // (the spine's hard exclusion). + package license import ( @@ -181,7 +179,7 @@ const ( // obligation on each other. Each feed's record is a delimited block inside // it — see BodyBeginMarker. // - // It is a record, not evidence. See the package doc. + // It is a record, not evidence. See the comment at the top of this file. NotesFileName = "LICENSE-NOTES.md" // LicenseFileName is the record each TIER 2 source directory carries on diff --git a/internal/ingest/poller/doc.go b/internal/ingest/poller/doc.go new file mode 100644 index 0000000..c303a62 --- /dev/null +++ b/internal/ingest/poller/doc.go @@ -0,0 +1,2 @@ +// Package poller is Lane A's authenticated conditional-GET poller. +package poller diff --git a/internal/ingest/poller/poller.go b/internal/ingest/poller/poller.go index f9c8d9a..3233c7e 100644 --- a/internal/ingest/poller/poller.go +++ b/internal/ingest/poller/poller.go @@ -1,5 +1,3 @@ -// Package poller is Lane A's authenticated conditional-GET poller. -// // =========================================================================== // WHAT THIS PACKAGE IS FOR // =========================================================================== @@ -104,6 +102,7 @@ // in that declaration and nowhere else. They are a fact about a PROVIDER's rate // limiter that several feeds happen to share, not a fact about any feed — see // the declaration. + package poller import ( @@ -738,7 +737,7 @@ func (p *Poller) Poll(ctx context.Context, feed config.FeedConfig) (PollResult, return res, err } - // The licence gate BEFORE THE NETWORK. See the package comment: a feed whose licence + // The licence gate BEFORE THE NETWORK. See the comment at the top of this file: a feed whose licence // evidence is absent is not fetched, because the bytes could not be kept // and the request would still have cost budget. decision, err := p.resolveLicense(feed) diff --git a/internal/ingest/reconcile/baseline.go b/internal/ingest/reconcile/baseline.go index cb725f8..f0e1a1e 100644 --- a/internal/ingest/reconcile/baseline.go +++ b/internal/ingest/reconcile/baseline.go @@ -1,5 +1,3 @@ -// Package reconcile owns the WEEKLY FULL-BASELINE SELF-HEAL. -// // This is the weekly self-heal (plan node upkeep). Lane A is the // zero-inference half of Anvil (the spine's corrected-requirements table): CVE/OSV/GHSA describe // vulnerable PACKAGE VERSIONS and a version comparator answers that exactly @@ -102,6 +100,7 @@ // imported zero records all increment that counter in the LIVE cache and are // named in the report. See recordFailure for the one case that deliberately // does not increment it, and why. + package reconcile import ( diff --git a/internal/ingest/reconcile/doc.go b/internal/ingest/reconcile/doc.go new file mode 100644 index 0000000..581c319 --- /dev/null +++ b/internal/ingest/reconcile/doc.go @@ -0,0 +1,2 @@ +// Package reconcile owns the WEEKLY FULL-BASELINE SELF-HEAL. +package reconcile diff --git a/internal/ingest/reconcile/reconcile_test.go b/internal/ingest/reconcile/reconcile_test.go index d159458..4ad0bb5 100644 --- a/internal/ingest/reconcile/reconcile_test.go +++ b/internal/ingest/reconcile/reconcile_test.go @@ -52,9 +52,8 @@ // httptest. NO TEST USES A REAL CREDENTIAL. // // A green run does NOT prove the self-heal is affordable against a 300,000 -// record cvelistV5 baseline; the fixtures here are tens of records. Nor does -// it prove anything about `go test -race`, which cannot run on the Windows dev -// host this was written on. +// record cvelistV5 baseline; the fixtures here are tens of records. `go test -race` covers +// this file on the Debian development machine (measured 2026-10-03) and in CI. package reconcile import ( @@ -1779,7 +1778,6 @@ func TestIntegrationNotesForTheManualRun(t *testing.T) { " - affordability. The fixtures are tens of records; a real cvelistV5 baseline is ~300,000", " records and ~570 MB, and neither the wall time of the merge join nor the disk cost of the", " scratch database has been measured against one.", - " - go test -race. It cannot run on the Windows dev host (cgo.exe exit 2); CI runs it on Linux.", " - that the bulk bootstrap's decoder and delta ingestion's decoder agree about every REAL publisher document. They agree", " about the synthetic CVE 5.1 documents in this file, which is what makes the 'matched' count", " meaningful here; a disagreement on a real corpus would surface as a wall of 'divergent' rows", diff --git a/internal/ingest/sanitize/doc.go b/internal/ingest/sanitize/doc.go new file mode 100644 index 0000000..3ddb5a5 --- /dev/null +++ b/internal/ingest/sanitize/doc.go @@ -0,0 +1,3 @@ +// Package sanitize is Lane A's sanitizer: ingest-time neutralisation of hostile +// text, plus the `anvil/trust` stamp that says where the bytes came from. +package sanitize diff --git a/internal/ingest/sanitize/sanitize.go b/internal/ingest/sanitize/sanitize.go index d83783f..0066c08 100644 --- a/internal/ingest/sanitize/sanitize.go +++ b/internal/ingest/sanitize/sanitize.go @@ -1,6 +1,3 @@ -// Package sanitize is Lane A's sanitizer: ingest-time neutralisation of hostile -// text, plus the `anvil/trust` stamp that says where the bytes came from. -// // =========================================================================== // WHY THIS RUNS AT INGEST AND NOWHERE ELSE // =========================================================================== @@ -465,6 +462,7 @@ // deleting text a reader might have seen is the survivable failure. The other // direction — keeping a span a reader will NOT see — is the one that puts // agent instructions in front of a repo-credentialed agent. + package sanitize import ( @@ -679,7 +677,7 @@ type SanitizeStats struct { // HiddenTagText counts HTML TAG tokens carrying text beyond their own tag // name — attributes, junk after an end tag name, and the content of the // raw-text elements. NOTHING WAS REMOVED FOR THIS COUNT. Tags are out of - // scope for removal (see the ruling in the package comment); this counter + // scope for removal (see the ruling in the comment at the top of this file); this counter // exists so that "stats=clean" stops being the answer for a string that a // renderer would partly hide. HiddenTagText int @@ -997,7 +995,7 @@ func IngestSlice(raw []string) ([]record.TrustedString, SanitizeStats) { } // stripRunes runs the rune classification. It is the first of Sanitize's two -// passes and it must stay first: see the package comment on ordering. +// passes and it must stay first: see the header comment on ordering. // // An invalid UTF-8 byte is DROPPED rather than replaced with U+FFFD. Replacing // preserves the position of the damage, which is friendlier to a human reading @@ -1096,7 +1094,7 @@ func (s *SanitizeStats) count(cat category) { // --------------------------------------------------------------------------- // Hidden markup: comment spans and the four other productions that render as -// nothing. See "WHICH HTML PRODUCTIONS ARE IN SCOPE" in the package comment +// nothing. See "WHICH HTML PRODUCTIONS ARE IN SCOPE" in the comment at the top of this file // for the scope decision and for what is deliberately left alone. // --------------------------------------------------------------------------- @@ -1482,7 +1480,7 @@ func indexCloseTag(s string, from int, name string) int { // IT IS NOT PART OF THE SANITIZE POST-CONDITION, and AssertSanitized does not // call it. Sanitize does not remove these spans, so a string that satisfies // AssertSanitized may still fail this; that asymmetry is the ruling in the -// package comment, made checkable. The remedy for a failure is to escape or +// header comment, made checkable. The remedy for a failure is to escape or // fence the text at the display site, never to delete it here. func AssertNoHiddenTagText(s string) error { tags, runes, offset := scanHiddenTagText(s) @@ -1521,7 +1519,7 @@ func AssertNoHiddenTagText(s string) error { // DOCTYPE opener. // nil DOES NOT mean every character in s is visible to a reader. HTML // MEAN TAGS are out of scope for removal (see the ruling in the -// package comment) and a tag can hide text. The nearest +// header comment) and a tag can hide text. The nearest // available check is AssertNoHiddenTagText; the actual // control is rendering s as plain text. func AssertSanitized(s string) error { diff --git a/internal/ingest/sanitize/sanitize_test.go b/internal/ingest/sanitize/sanitize_test.go index f949ea3..d9b2941 100644 --- a/internal/ingest/sanitize/sanitize_test.go +++ b/internal/ingest/sanitize/sanitize_test.go @@ -955,7 +955,7 @@ func TestNoDefaultIgnorableCodePointSurvives(t *testing.T) { // text past AssertSanitized -- silently, on a toolchain bump. t.Fatalf("this Go toolchain ships Other_Default_Ignorable_Code_Point=%v and "+ "Variation_Selector=%v, so the default-ignorable sweep was NOT checked. This fails "+ - "rather than skips: see internal/SKIPPED-CONTROLS.md.", odi != nil, vs != nil) + "rather than skips: see docs/controls.md.", odi != nil, vs != nil) } n := 0 for r := rune(0); r <= unicode.MaxRune; r++ { diff --git a/internal/match/apk_compare.go b/internal/match/apk_compare.go index bfee52e..c9042a4 100644 --- a/internal/match/apk_compare.go +++ b/internal/match/apk_compare.go @@ -226,6 +226,7 @@ // line 732 `6.0_p1 > 6.0`), and the three R8 shapes, which appear in no row of // it at all. Those vectors are tagged AUTHORED and name the rule they come // from; they may not name a file, because there is no line to name. + package match import ( diff --git a/internal/match/comparator.go b/internal/match/comparator.go index 4a3693c..c1a4221 100644 --- a/internal/match/comparator.go +++ b/internal/match/comparator.go @@ -1,7 +1,3 @@ -// Package match is Lane A's deterministic version comparator: the comparator of -// plan/design/lane-a.md, and the component every other Lane A step -// feeds. -// // =========================================================================== // WHAT THIS PACKAGE IS // =========================================================================== @@ -178,6 +174,7 @@ // - There is no clock. `as_of` and `detected_at` belong to the collector // and to record emission's record emitter; a second time source here would be a // second answer to a question already owned elsewhere. + package match import ( @@ -816,8 +813,8 @@ func epochSpelling(scheme Scheme, v string) (present bool, value int, ok bool) { // advisory endpoint spelled `2.34-100.el9` gives `2 > 0`, so the installed // version sorts ABOVE the fixed endpoint, the range does not contain it, and // the run reports zero findings, zero refusals, Complete=true and a clean -// verdict on a vulnerable host. That is the false negative the package doc's -// first paragraph names as the worst output this lane can produce, on one of +// verdict on a vulnerable host. That is the false negative the header comment's +// first section names as the worst output this lane can produce, on one of // the most common shapes in the RPM world. // // =========================================================================== @@ -1139,7 +1136,7 @@ func (d Defence) sortKey() string { // UpstreamOnlyAdvisory is the package-level residue of the vendor-first // policy: an advisory that was decided by an upstream range for a package // which HAS vendor coverage for some other advisory. It is REPORTED, not -// suppressed — see the package doc's "SCOPE OF THE PRECEDENCE". +// suppressed — see the header comment's "SCOPE OF THE PRECEDENCE". type UpstreamOnlyAdvisory struct { Ecosystem string Package string @@ -1174,7 +1171,7 @@ func (u UpstreamOnlyAdvisory) sortKey() string { // this package cannot fix it — a vendor row and an upstream row that share no // identifier cannot be shown to be about the same flaw, and guessing that // they are (by package name, say) is the package-scoped suppression the -// package doc rejects as an unbounded false-negative generator. +// header comment rejects as an unbounded false-negative generator. // // What it CAN do is stop the dependence being invisible. Every vendor range // that arrives without an alias is listed here, so "the defence did not fire" diff --git a/internal/match/comparator_test.go b/internal/match/comparator_test.go index c18dcc9..bc561f5 100644 --- a/internal/match/comparator_test.go +++ b/internal/match/comparator_test.go @@ -3642,6 +3642,8 @@ func TestNoSourceFileReachesForAClockOrARandomSource(t *testing.T) { expected := map[string]bool{ "comparator.go": true, "dpkg_compare.go": true, "rpm_compare.go": true, "apk_compare.go": true, "purl.go": true, "comparator_test.go": true, + // The package summary, and nothing else (test/hygiene's package-doc guard). + "doc.go": true, // The transcribed corpus. It is a separate file because it is // GENERATED from the three published suites rather than written, // and mixing a generated table into a hand-written test file is @@ -3683,8 +3685,8 @@ func TestNoSourceFileReachesForAClockOrARandomSource(t *testing.T) { return true }) } - if seen != 5 { - t.Errorf("scanned %d non-test files, expected 5", seen) + if seen != 6 { + t.Errorf("scanned %d non-test files, expected 6", seen) } } diff --git a/internal/match/doc.go b/internal/match/doc.go new file mode 100644 index 0000000..c105e86 --- /dev/null +++ b/internal/match/doc.go @@ -0,0 +1,4 @@ +// Package match is Lane A's deterministic version comparator: the comparator of +// plan/design/lane-a.md, and the component every other Lane A step +// feeds. +package match diff --git a/internal/match/dpkg_compare.go b/internal/match/dpkg_compare.go index fd42c2d..5941ec9 100644 --- a/internal/match/dpkg_compare.go +++ b/internal/match/dpkg_compare.go @@ -64,6 +64,7 @@ // any published comparison table can contain it. dpkgValidity in // corpus_transcribed_test.go transcribes the nine is_valid() assertions from // Dpkg_Version.t for exactly this reason. + package match import ( diff --git a/internal/match/purl.go b/internal/match/purl.go index 7d7a8c0..722d516 100644 --- a/internal/match/purl.go +++ b/internal/match/purl.go @@ -31,11 +31,12 @@ // for three times over: a denylist loses, because the string nobody listed is // the one that walks through. // -// The practical consequence is stated plainly in the package doc: this +// The practical consequence is stated plainly in comparator.go's header comment: this // comparator covers `deb`, `rpm` and `apk`, and refuses `npm`, `pypi`, // `golang`, `maven`, `nuget`, `cargo`, `gem`, `composer` and everything else. // A refusal is a visible gap. A fallback to semver, or to a lexical compare, // would be an invisible wrong answer. + package match import ( diff --git a/internal/match/rpm_compare.go b/internal/match/rpm_compare.go index 4285393..72ee8ab 100644 --- a/internal/match/rpm_compare.go +++ b/internal/match/rpm_compare.go @@ -74,6 +74,7 @@ // the published suite minus the rows the implementation fails is a corpus // filtered by the implementation, and that circularity is what this project's // licence-marker table already paid for once. + package match import ( diff --git a/internal/mirror/accelerator/accelerator_test.go b/internal/mirror/accelerator/accelerator_test.go index cc1b09f..aa458f7 100644 --- a/internal/mirror/accelerator/accelerator_test.go +++ b/internal/mirror/accelerator/accelerator_test.go @@ -1119,7 +1119,7 @@ func TestAFullWarmStartLeavesEveryTierDirectoryUntouched(t *testing.T) { // a directory that passes every string check and resolves elsewhere. // // THIS TEST USED TO SKIP ON WINDOWS, AND THAT SKIP IS THE FIRST OF THE TWO -// INCIDENTS internal/SKIPPED-CONTROLS.md exists for. os.Symlink needs +// INCIDENTS docs/controls.md exists for. os.Symlink needs // SeCreateSymbolicLinkPrivilege (Developer Mode or an elevated shell), so on an // ordinary Windows host the whole guard reported SUCCESS while checking // nothing, and the package still printed ok. A Windows DIRECTORY JUNCTION then diff --git a/internal/mirror/accelerator/doc.go b/internal/mirror/accelerator/doc.go new file mode 100644 index 0000000..e8e51cf --- /dev/null +++ b/internal/mirror/accelerator/doc.go @@ -0,0 +1,3 @@ +// Package accelerator is Anvil's OPTIONAL warm-start cache for package-range +// matching. This is the accelerator (plan node accelerator). +package accelerator diff --git a/internal/mirror/accelerator/reparse.go b/internal/mirror/accelerator/reparse.go index 3afb619..047b73e 100644 --- a/internal/mirror/accelerator/reparse.go +++ b/internal/mirror/accelerator/reparse.go @@ -37,7 +37,7 @@ package accelerator // understand IO_REPARSE_TAG_MOUNT_POINT. It is written in portable Go against // os and path/filepath only — no cgo, no golang.org/x/sys, no syscall, no build // tags — so on Linux and macOS it behaves exactly as symlink resolution always -// did, and the CI run and the Windows dev host execute the same code on the same +// did, and a Linux run and a Windows run execute the same code on the same // question. (Only the test that BUILDS a junction is platform-specific, in // reparse_windows_test.go, and it reaches package syscall rather than adding a // dependency.) diff --git a/internal/mirror/accelerator/reparse_test.go b/internal/mirror/accelerator/reparse_test.go index 4e9be76..9e5d640 100644 --- a/internal/mirror/accelerator/reparse_test.go +++ b/internal/mirror/accelerator/reparse_test.go @@ -258,7 +258,7 @@ func assertSamePath(t *testing.T, got, want string) { // A-1 itself. // // TestSymlinkedCacheRootIntoTheQuarantineIsRefused reported SKIP four times on -// the Windows dev host and the package still printed +// the Windows development machine and the package still printed // // ok github.com/Susquehanna-Syntax/Anvil/internal/mirror/accelerator // diff --git a/internal/mirror/accelerator/trivydb.go b/internal/mirror/accelerator/trivydb.go index 9631527..e64971d 100644 --- a/internal/mirror/accelerator/trivydb.go +++ b/internal/mirror/accelerator/trivydb.go @@ -1,6 +1,3 @@ -// Package accelerator is Anvil's OPTIONAL warm-start cache for package-range -// matching. This is the accelerator (plan node accelerator). -// // # ANVIL MUST WORK CORRECTLY WITHOUT THIS PACKAGE. THAT IS THE DESIGN. // // Everything here is a performance optimisation. If the accelerator is absent, @@ -137,6 +134,7 @@ // what RegistryBase is for. It is stated here because no test can observe // it: the spine's safety section forbids the network at test time, so the // mock registry does not model the redirect the real one issues. + package accelerator import ( @@ -619,8 +617,8 @@ const caseInsensitivePaths = runtime.GOOS == "windows" || runtime.GOOS == "darwi // onto the tier form, so it can only ever refuse more — and refusing a cache // root that spells itself "Tier2" costs nothing, because no legitimate cache // root distinguishes Tier2 from tier2. Making it conditional would mean the -// Linux CI run tests different code from the Windows dev host, on exactly the -// question the Windows host got wrong. +// Linux run tests different code from a Windows run, on exactly the question +// the Windows build got wrong. func foldPathComponent(c string) string { if c == "" || c == "." || c == ".." { return c diff --git a/internal/policy/doc.go b/internal/policy/doc.go new file mode 100644 index 0000000..71efcd8 --- /dev/null +++ b/internal/policy/doc.go @@ -0,0 +1,9 @@ +// Package policy resolves Anvil's trigger policy from the repository it is +// scanning. Trigger policy is DATA: which events fire a scan, which refs and +// paths they apply to, which semver bumps gate a full scan, and on what +// cadence the daemon re-scans are all read from a file in the repository, never +// compiled into Anvil. The spine's corrected-requirements table makes that a hard constraint, and +// plan/design/control-plane.md restates the review rule it implies: a literal +// such as "push" or "major" used as a match condition anywhere outside the +// parser is a defect. +package policy diff --git a/internal/policy/locate.go b/internal/policy/locate.go index 0fff674..7310a72 100644 --- a/internal/policy/locate.go +++ b/internal/policy/locate.go @@ -1,12 +1,3 @@ -// Package policy resolves Anvil's trigger policy from the repository it is -// scanning. Trigger policy is DATA: which events fire a scan, which refs and -// paths they apply to, which semver bumps gate a full scan, and on what -// cadence the daemon re-scans are all read from a file in the repository, never -// compiled into Anvil. The spine's corrected-requirements table makes that a hard constraint, and -// plan/design/control-plane.md restates the review rule it implies: a literal -// such as "push" or "major" used as a match condition anywhere outside the -// parser is a defect. -// // This file is the policy schema's half of that: FINDING the policy file. Parsing it, // evaluating its rules (the policy engine), and computing the semver bump its rules match // against (semver bump classification) are separate steps in this same package. @@ -18,6 +9,7 @@ // side -- so a second schema for this one file, in the dynamic tier or in the GitHub // Action (the GitHub Action), would be the eleventh. Consumers validate against that file and // extend it there. + package policy import ( diff --git a/internal/record/FINGERPRINT-SPEC.md b/internal/record/FINGERPRINT-SPEC.md index 59eb766..b67875c 100644 --- a/internal/record/FINGERPRINT-SPEC.md +++ b/internal/record/FINGERPRINT-SPEC.md @@ -8,7 +8,7 @@ be able to read the complete algorithm here and nowhere else, and emit byte-iden `plan/design/record-and-store.md`'s "Fingerprint Specification" section remains the record of *why* the research-branch conflict was resolved the way it was. It is a **summary**, not the definition. -`internal/record/REVIEW-contract-and-fingerprint.md` (finding 1) proved the summary insufficient by re-implementing its +`docs/reviews/contract-and-fingerprint.md` (finding 1) proved the summary insufficient by re-implementing its four-clause `normalized_match` text in Python and obtaining `55e27b07…` where the committed golden for `sast-01-go-sql-string-concat` is `13c60ccf…`. The orchestrator ruled on 2026-08-08 that the implementation was right and the specification incomplete, and that the fix was to write the diff --git a/internal/record/contract.go b/internal/record/contract.go index 0e28aea..1ad9cae 100644 --- a/internal/record/contract.go +++ b/internal/record/contract.go @@ -1,8 +1,3 @@ -// Package record defines Anvil's single frozen audit-record contract: the -// SARIF 2.1.0 wire shape Anvil produces, the typed `anvil/*` property-bag -// extension that carries what stock SARIF cannot, and — since the -// 2026-08-07 orchestrator ruling — every enum shared across Anvil's areas. -// // # Authority // // This file is the single point where shared cross-area vocabulary is fixed. @@ -51,6 +46,7 @@ // Specification", "Store Schema"); research/18-unified-audit-record.md // ("Recommendation For Anvil", the annotated record, Risks); // research/24-coding-agent-consumption.md ("What the audit record must carry"). + package record import ( diff --git a/internal/record/correlation.go b/internal/record/correlation.go index dca5ade..658ee0f 100644 --- a/internal/record/correlation.go +++ b/internal/record/correlation.go @@ -80,7 +80,8 @@ // the annotated record's anvil/correlation block); the spine's safety section; // plan/design/record-and-store.md (Open Questions #1). // -// (Free-floating file comment: contract.go carries the package doc.) +// (Free-floating file comment: doc.go carries the package doc.) + package record import ( diff --git a/internal/record/doc.go b/internal/record/doc.go new file mode 100644 index 0000000..5617de7 --- /dev/null +++ b/internal/record/doc.go @@ -0,0 +1,5 @@ +// Package record defines Anvil's single frozen audit-record contract: the +// SARIF 2.1.0 wire shape Anvil produces, the typed `anvil/*` property-bag +// extension that carries what stock SARIF cannot, and — since the +// 2026-08-07 orchestrator ruling — every enum shared across Anvil's areas. +package record diff --git a/internal/record/fingerprint.go b/internal/record/fingerprint.go index 4cc72cf..ae87617 100644 --- a/internal/record/fingerprint.go +++ b/internal/record/fingerprint.go @@ -143,7 +143,7 @@ package record // THE AUTHORITATIVE SPECIFICATION IS internal/record/FINGERPRINT-SPEC.md // =========================================================================== // -// The contract-and-fingerprint review (REVIEW-contract-and-fingerprint.md) +// The contract-and-fingerprint review (docs/reviews/contract-and-fingerprint.md) // proved that the four-clause `normalized_match` text in // plan/design/record-and-store.md is NOT sufficient to reproduce this file's // digests: a re-implementation written from that text alone emits 55e27b07... diff --git a/internal/record/fingerprint_conformance_test.go b/internal/record/fingerprint_conformance_test.go index e52a690..bf07ef6 100644 --- a/internal/record/fingerprint_conformance_test.go +++ b/internal/record/fingerprint_conformance_test.go @@ -81,7 +81,7 @@ package record // // Sources: the spine's record section; the fingerprint conformance harness' design; // internal/record/FINGERPRINT-SPEC.md (the algorithm, and Appendix Z); -// internal/record/REVIEW-contract-and-fingerprint.md finding 1 (why the specification had to be +// docs/reviews/contract-and-fingerprint.md finding 1 (why the specification had to be // written down completely before this test could exist at all). import ( diff --git a/internal/record/fingerprint_spec_test.go b/internal/record/fingerprint_spec_test.go index 02868a7..8a3d7da 100644 --- a/internal/record/fingerprint_spec_test.go +++ b/internal/record/fingerprint_spec_test.go @@ -2,7 +2,7 @@ package record // fingerprint_spec_test.go — the test that keeps FINGERPRINT-SPEC.md honest. // -// The contract-and-fingerprint review (REVIEW-contract-and-fingerprint.md), +// The contract-and-fingerprint review (docs/reviews/contract-and-fingerprint.md), // blocker 1: `normalized_match` was defined only in Go. The critic // re-implemented plan/design/record-and-store.md's four-clause spec text in // Python and got 55e27b07… where the committed golden for sast-01 is 13c60ccf…. diff --git a/internal/record/lanea/doc.go b/internal/record/lanea/doc.go new file mode 100644 index 0000000..0bbe64a --- /dev/null +++ b/internal/record/lanea/doc.go @@ -0,0 +1,3 @@ +// Package lanea populates the Lane-A-owned fields of Anvil's canonical audit +// record: record emission (plan node emission). +package lanea diff --git a/internal/record/lanea/emit.go b/internal/record/lanea/emit.go index b878992..3dab995 100644 --- a/internal/record/lanea/emit.go +++ b/internal/record/lanea/emit.go @@ -1,6 +1,3 @@ -// Package lanea populates the Lane-A-owned fields of Anvil's canonical audit -// record: record emission (plan node emission). -// // =========================================================================== // WHAT THIS PACKAGE IS, AND THE ONE THING IT IS NOT // =========================================================================== @@ -194,6 +191,7 @@ // (RefusalTrustValidationStep), and the named step travels on // Emission.TrustValidationStep — the same treatment, and the same reported // record-contract gap, as license_manual_note in deviation 1. + package lanea import ( diff --git a/internal/record/readpath.go b/internal/record/readpath.go index b7abf56..197392b 100644 --- a/internal/record/readpath.go +++ b/internal/record/readpath.go @@ -87,6 +87,7 @@ // path", the annotated Tier-1 task card); research/24-coding-agent-consumption // .md ("What the audit record must carry"); plan/design/record-and-store.md // (the read path); the spine's corrected-requirements, record and safety sections. + package record import ( @@ -1210,8 +1211,7 @@ func formatTime(t time.Time) string { // cardPath is the Tier-1 path for a finding id. // // Finding ids are `sast:8c1e4b0f…` in research/18's own example, and a colon -// is not a legal path character on Windows — where this project is being -// developed. The sanitisation is a total, deterministic function so the same +// is not a legal path character on Windows, where the test suite also runs. The sanitisation is a total, deterministic function so the same // finding always lands at the same path on every platform. func (rd *Reader) cardPath(findingID string) string { return rd.cardPrefix() + SanitizeCardFilename(findingID) + ".json" diff --git a/internal/record/sarif_github.go b/internal/record/sarif_github.go index 7c0df87..82fa2b2 100644 --- a/internal/record/sarif_github.go +++ b/internal/record/sarif_github.go @@ -136,7 +136,7 @@ package record // visible rather than absorbed. fingerprint.go says the key "is owned by the // GitHub projection (the GitHub projection)"; plan/design/record-and-store.md's Record Field // Contract names the producer as the fingerprint engine; and -// internal/record/REVIEW-contract-and-fingerprint.md's MAJOR 3 records that the disagreement is +// docs/reviews/contract-and-fingerprint.md's MAJOR 3 records that the disagreement is // unruled and that no code in the tree produces the value today. The GitHub projection's // packet scopes this file to the projection and says nothing about producing // a fingerprint. diff --git a/internal/record/sealing.go b/internal/record/sealing.go index f9f15aa..9c1817f 100644 --- a/internal/record/sealing.go +++ b/internal/record/sealing.go @@ -58,7 +58,7 @@ // onto those columns. Every literal it produces comes from contract.go, which // is the same source ddl_test.go checks the SQL against. // -// (Free-floating file comment: contract.go carries the package doc.) +// (Free-floating file comment: doc.go carries the package doc.) package record diff --git a/internal/record/taskcard.go b/internal/record/taskcard.go index 65cc0f5..7c98a6a 100644 --- a/internal/record/taskcard.go +++ b/internal/record/taskcard.go @@ -71,6 +71,7 @@ // `evidence_class`, `dast.reproduction`, `risk.*`, `locus.*`, // `advisory_excerpt` (<=800 tokens) and `group_id`. Every one has a field // below, and TestCardCarriesTheNonNegotiableFields asserts it. + package record import ( diff --git a/internal/scanctl/controller_review_test.go b/internal/scanctl/controller_review_test.go index 15ade3b..4956c32 100644 --- a/internal/scanctl/controller_review_test.go +++ b/internal/scanctl/controller_review_test.go @@ -369,9 +369,9 @@ func TestWriteGuardsConsultTheSealerNotTheCallersSnapshot(t *testing.T) { // saved the old design, which is why the fix was to move the buffers onto the // Controller rather than to write a warning. // -// -race cannot run on the Windows dev host; CI runs this on ubuntu-latest, where -// this repository has already had one concurrency bug that passed every local -// run (internal/handoff/reaper.go:415). +// Run it under -race: locally on Linux and in CI on ubuntu-latest. This +// repository has already had one concurrency bug that only -race found +// (internal/handoff/reaper.go's Run). func TestConcurrentFanInLosesNoFindings(t *testing.T) { const ( workers = 8 diff --git a/internal/scanctl/deadlines.go b/internal/scanctl/deadlines.go index 94dbbf1..64396a4 100644 --- a/internal/scanctl/deadlines.go +++ b/internal/scanctl/deadlines.go @@ -1,15 +1,6 @@ -// Package scanctl is `anvil-scanctl`: the ONE named scan controller -// the spine's one-controller rule requires, holding ONE state machine with ONE owner. -// The spine's one-controller rule exists because four research branches each specified part -// of an orchestrator (a consumption protocol with leases and ledgers, a -// correlator process, sixteen validation gates, a target-lifecycle harness), -// and "implement it as one named scan controller with one state machine and -// one owner, or it will be re-implemented inconsistently in four places." -// -// This file (the deadline design) carries the package doc because it is the package's -// first file and the one every other file in it depends on. Later files in -// this package — statemachine.go (the controller's state wiring), handoff.go -// (the handoff adapter) — must NOT add a second package comment. +// This file is the deadline design, the one every other file in this package +// depends on. The package doc lives in doc.go; no other file may attach a +// comment to its package clause (test/hygiene's package-doc guard checks). // // scanctl OWNS NO VOCABULARY. Every enum token it handles is a Go constant // from internal/record, which the shared-vocabulary review makes the single @@ -19,6 +10,7 @@ // separate authors each defining the shared vocabulary from their own side. // A bare string literal for an enum value in this package is a second // definition and is how that recurs. + package scanctl import ( diff --git a/internal/scanctl/doc.go b/internal/scanctl/doc.go new file mode 100644 index 0000000..3cf4ad5 --- /dev/null +++ b/internal/scanctl/doc.go @@ -0,0 +1,8 @@ +// Package scanctl is `anvil-scanctl`: the ONE named scan controller +// the spine's one-controller rule requires, holding ONE state machine with ONE owner. +// The spine's one-controller rule exists because four research branches each specified part +// of an orchestrator (a consumption protocol with leases and ledgers, a +// correlator process, sixteen validation gates, a target-lifecycle harness), +// and "implement it as one named scan controller with one state machine and +// one owner, or it will be re-implemented inconsistently in four places." +package scanctl diff --git a/internal/scanctl/handoff.go b/internal/scanctl/handoff.go index d73dd25..ac0b84b 100644 --- a/internal/scanctl/handoff.go +++ b/internal/scanctl/handoff.go @@ -141,7 +141,7 @@ // with the grant. A Go re-check here would be a second definition that // could disagree, and it could not be atomic with anything. // -// (Free-floating file comment: deadlines.go carries the package doc.) +// (Free-floating file comment: doc.go carries the package doc.) package scanctl diff --git a/internal/scanctl/statemachine.go b/internal/scanctl/statemachine.go index bdb76d6..b5d6595 100644 --- a/internal/scanctl/statemachine.go +++ b/internal/scanctl/statemachine.go @@ -102,7 +102,7 @@ // "two areas meaning different things by the same field name" class the // shared-vocabulary review was convened over. // -// (Free-floating file comment: deadlines.go carries the package doc.) +// (Free-floating file comment: doc.go carries the package doc.) package scanctl @@ -820,8 +820,8 @@ func NewController(policy DeadlinePolicy, marks WatermarkPolicy) (*Controller, e // IT TAKES THE MUTEX, for the same reason record.Sealer.SetClock takes its own // (controller-core finding m4). Construction-time use was always safe; a daemon that re-clocks at // runtime raced four readers — Transition, applyTick, publish and NextWake — -// and the race detector cannot run on the Windows dev host, so only CI would -// ever have seen it. +// and only the race detector sees that, so it must run (locally on Linux and in +// CI) for this to stay checked. func (c *Controller) SetClock(now func() time.Time) { if now == nil { now = time.Now diff --git a/internal/store/ddl.go b/internal/store/ddl.go index c7845ac..079e13e 100644 --- a/internal/store/ddl.go +++ b/internal/store/ddl.go @@ -1,5 +1,3 @@ -// Package store owns Anvil's single SQLite store of record. -// // The spine's corrected-requirements table collapsed the originally-specified "8-hour buffer file" // into one SQLite database, a `handoff` table, and a regenerable tmpfs packet // that is never a source of truth. This package holds the DDL for that @@ -12,6 +10,7 @@ // enough read-only introspection for the migration runner's migration ledger and for the test // that proves the SQL vocabularies and internal/record's Go enums have not // drifted apart. It opens no database and executes no statement. + package store import ( diff --git a/internal/store/doc.go b/internal/store/doc.go new file mode 100644 index 0000000..b7ce4a4 --- /dev/null +++ b/internal/store/doc.go @@ -0,0 +1,2 @@ +// Package store owns Anvil's single SQLite store of record. +package store diff --git a/test/conformance/lanea/lanea_test.go b/test/conformance/lanea/lanea_test.go index e1da89c..fc76c5b 100644 --- a/test/conformance/lanea/lanea_test.go +++ b/test/conformance/lanea/lanea_test.go @@ -107,9 +107,10 @@ // on this machine, and the corpus probe alone when none is. // // Both branches run the full chain. What differs is what the ledger may claim: -// on a Linux CI host the host-inventory link is PROVEN and names the collected -// package count, and on a developer's Windows machine it stays UNPROVEN and -// says so. THE LEDGER LINE NAMES WHICH — a run that proved the collector and a +// on a Linux host with a package manager (CI, and the Debian development +// machine since 2026-10-03) the host-inventory link is PROVEN and names the +// collected package count, and on a host with none it stays UNPROVEN and says +// so. THE LEDGER LINE NAMES WHICH — a run that proved the collector and a // run that proved only a file must not render the same, because telling those // two apart is the entire point of that link. // @@ -2009,8 +2010,8 @@ func hostSourceProblems(src hostInventorySource, collectedSubmitted int) []strin // TestTheHostLedgerLineNamesWhichInventoryItUsed is the control for the one // thing the host link exists to communicate. // -// THE REAL PATH CANNOT RUN ON A WINDOWS DEVELOPMENT HOST — there is no dpkg, -// rpm or apk, so host.Collect refuses and the chain above takes the probe +// THE REAL PATH CANNOT RUN ON A HOST WITHOUT dpkg, rpm OR apk (any non-Linux +// host, a minimal container), so host.Collect refuses and the chain above takes the probe // branch every time. Everything DOWNSTREAM of that call is host-independent // and is exercised here against two synthetic sources: a run that collected // real packages and a run that did not. They must not render the same, in the @@ -2168,8 +2169,8 @@ func TestTheHostTripwiresFireOnDroppedEvidence(t *testing.T) { // not the best thing this machine can offer. // // It is deliberately not a statement about which branch is correct HERE: on a -// Windows development host it asserts the probe branch, on a Linux CI host it -// asserts the collected branch, and either way it asserts that the chain and +// host with no package manager it asserts the probe branch, on a Linux host +// with one it asserts the collected branch, and either way it asserts that the chain and // the machine agree. func TestTheChainUsesTheBestAvailableHostEvidence(t *testing.T) { _, err := host.Collect(context.Background(), host.Options{ diff --git a/test/hygiene/package_docs_test.go b/test/hygiene/package_docs_test.go new file mode 100644 index 0000000..6557405 --- /dev/null +++ b/test/hygiene/package_docs_test.go @@ -0,0 +1,170 @@ +package hygiene_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "path" + "path/filepath" + "sort" + "strings" + "testing" +) + +// go doc concatenates every comment attached to a package clause, in file +// order, and shows the first sentence as the package summary. Before plan node +// packagedocs, several packages attached file comments to their package +// clause, so go doc introduced the authorization kernel with one of its phases, +// the attack-surface inventory with its authentication helper and the +// comparator with Alpine's version ordering. The rule this guard keeps: +// +// - every package with non-test Go source has a doc.go; +// - doc.go holds the package summary and nothing else (no imports, no +// declarations), starting "Package " or, for a command, +// "Command "; +// - no other non-test file attaches a comment to its package clause. File +// comments stay, separated from the package line by a blank line. + +// docProblems checks one set of Go source files, keyed by slash-separated +// path relative to the module root. It is a pure function so the negative +// control can run the shipping check on a synthetic tree. +func docProblems(files map[string][]byte) []string { + byDir := map[string][]string{} + for p := range files { + if strings.HasSuffix(p, ".go") && !strings.HasSuffix(p, "_test.go") { + byDir[path.Dir(p)] = append(byDir[path.Dir(p)], p) + } + } + dirs := make([]string, 0, len(byDir)) + for d := range byDir { + dirs = append(dirs, d) + } + sort.Strings(dirs) + + var problems []string + for _, dir := range dirs { + paths := byDir[dir] + sort.Strings(paths) + hasDocGo := false + for _, p := range paths { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, p, files[p], parser.ParseComments) + if err != nil { + problems = append(problems, fmt.Sprintf("%s: does not parse: %v", p, err)) + continue + } + if path.Base(p) != "doc.go" { + if f.Doc != nil { + problems = append(problems, fmt.Sprintf("%s: a comment is attached to the package "+ + "clause, so go doc shows it as part of the package summary. Put a blank line "+ + "between it and the package line; the summary belongs in %s/doc.go", p, dir)) + } + continue + } + hasDocGo = true + want := "Package " + f.Name.Name + " " + if f.Name.Name == "main" { + want = "Command " + path.Base(dir) + " " + } + if f.Doc == nil || !strings.HasPrefix(f.Doc.Text(), want) { + problems = append(problems, fmt.Sprintf("%s: the package summary must start %q", p, want)) + } + if len(f.Imports) > 0 || len(f.Decls) > 0 { + problems = append(problems, fmt.Sprintf("%s: holds code; doc.go holds the package "+ + "summary and nothing else", p)) + } + for _, cg := range f.Comments { + if cg != f.Doc && !isBuildConstraint(cg) { + problems = append(problems, fmt.Sprintf("%s: carries a comment other than the "+ + "package summary", p)) + break + } + } + } + if !hasDocGo { + problems = append(problems, fmt.Sprintf("%s: no doc.go; every package states what it is "+ + "in one place", dir)) + } + } + return problems +} + +func isBuildConstraint(cg *ast.CommentGroup) bool { + return len(cg.List) == 1 && strings.HasPrefix(cg.List[0].Text, "//go:build ") +} + +func TestEveryPackageSummaryIsItsOwn(t *testing.T) { + root, err := filepath.Abs(filepath.Join("..", "..")) + if err != nil { + t.Fatal(err) + } + listed, how := listTree(t, root) + files := map[string][]byte{} + for _, rel := range listed { + top := strings.SplitN(rel, "/", 2)[0] + if !strings.HasSuffix(rel, ".go") || strings.Contains("/"+rel, "/testdata/") || top == "eval" { + continue + } + data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(rel))) + if err != nil { + continue // listed by git but deleted in the working tree + } + files[rel] = data + } + // The module has dozens of packages; a handful of files means the listing + // broke, and an empty set passes every rule. + if len(files) < 100 { + t.Fatalf("only %d Go files listed (%s); that is a broken listing, not a small module", len(files), how) + } + for _, p := range docProblems(files) { + t.Error(p) + } +} + +// TestThePackageDocGuardFails is the negative control: each synthetic package +// breaks one rule, and the shipping check must name every one of them. +func TestThePackageDocGuardFails(t *testing.T) { + good := []byte("// Package good is fine.\npackage good\n") + cases := map[string]struct { + files map[string][]byte + want string + }{ + "attached file comment": {map[string][]byte{ + "a/doc.go": []byte("// Package a is fine.\npackage a\n"), + "a/b.go": []byte("// Phase 1 of something.\npackage a\n"), + }, "attached to the package clause"}, + "no doc.go": {map[string][]byte{ + "b/b.go": []byte("// File comment.\n\npackage b\n"), + }, "no doc.go"}, + "wrong summary": {map[string][]byte{ + "c/doc.go": []byte("// This package does things.\npackage c\n"), + }, `must start "Package c "`}, + "command summary": {map[string][]byte{ + "cmd/tool/doc.go": []byte("// Package main is a tool.\npackage main\n"), + }, `must start "Command tool "`}, + "code in doc.go": {map[string][]byte{ + "d/doc.go": []byte("// Package d is fine.\npackage d\n\nconst X = 1\n"), + }, "holds code"}, + "second comment in doc.go": {map[string][]byte{ + "e/doc.go": []byte("// Package e is fine.\npackage e\n\n// stray\n"), + }, "other than the package summary"}, + } + for name, c := range cases { + c.files["good/doc.go"] = good + got := strings.Join(docProblems(c.files), "\n") + if !strings.Contains(got, c.want) { + t.Errorf("%s: the guard did not report %q; it said:\n%s", name, c.want, got) + } + if strings.Contains(got, "good/") { + t.Errorf("%s: the guard refused the well-formed package: %s", name, got) + } + } + // Test files are not part of go doc and may carry their own package comment. + tests := map[string][]byte{"f/doc.go": []byte("// Package f is fine.\npackage f\n"), + "f/f_test.go": []byte("// Package f_test is external.\npackage f_test\n")} + if p := docProblems(tests); len(p) > 0 { + t.Errorf("a test file's package comment was refused: %v", p) + } +} diff --git a/test/hygiene/plan_codes_test.go b/test/hygiene/plan_codes_test.go index bf36f48..4a7cbd0 100644 --- a/test/hygiene/plan_codes_test.go +++ b/test/hygiene/plan_codes_test.go @@ -169,7 +169,7 @@ func TestThePatternsRefuseTheOldShapesAndNothingElse(t *testing.T) { "gates 4, 5, 8, 9 and 10", "Phase 4: close the confirmation gate", "the read-only-boundary review's finding M1(a)", - "U7 is recorded in internal/SKIPPED-CONTROLS.md", + "U7 is recorded in docs/controls.md", "SARIF 2.1.0 §3.27.4", "research/10-prior-art-and-landscape.md", "the confirmation gate's rules R1, R2 and R3",