All checks were successful
check / check (push) Successful in 3m2s
PR #89 stopped script/cibuild replaying cached check layers, but left a gap: a bare `docker build .` with no --build-arg still faked. An unset ARG is an empty string, an empty string is a stable cache key, and the check layers replay from it. That gap mattered because REPO_POLICIES.md names `docker build .` verbatim as a command that must be green, so the documented command was the one that lied. Both check stages now carry `RUN [ -n "$CHECK_EPOCH" ] || exit 1` immediately under their own ARG. Failed steps are never cached, so this fails on every invocation rather than once - a bare build now stops with a named error instead of reporting a green it did not earn. Each stage needs its own guard because ARG scope is per-stage; a gate-carrying stage without one is a silent hole if ordering ever changes. The check RUNs now reference the value (`echo "check epoch: ${CHECK_EPOCH}" && make <target>`), so the cache miss is contractual rather than resting on BuildKit's current treatment of unreferenced ARGs, and the epoch is visible in the build log. The epoch becomes "$(date +%s%N)$$" so concurrent invocations in the same second cannot collide. busybox silently drops %N and exits 0, so $$ is what makes it correct there. The bare-assignment form is retained deliberately: inlining the substitution into --build-arg would, under set -eu, yield an empty and therefore constant epoch without aborting. script/docker gets the same treatment - it is not the gate, but two entrypoints disagreeing about whether the tree is green is its own hazard, and local builds are almost always warm. Verified by negative control rather than inspection: a bare build fails twice consecutively here and succeeds twice on the parent commit, so the change is demonstrably not a no-op. The builder-stage guard was fired directly with a targeted probe build, since the lint stage otherwise fails first and would leave it unexercised.
186 lines
11 KiB
Markdown
186 lines
11 KiB
Markdown
# Workflow
|
|
|
|
* branch (from `main`)
|
|
* do the work in Next Step
|
|
* move Next Step to the top of Completed Steps
|
|
* move the top item of Future Steps into Next Step
|
|
* commit (`TODO.md` changes in the same commit as the work)
|
|
* merge to `main` if the branch is not protected, otherwise open a PR
|
|
* push
|
|
|
|
# Status
|
|
|
|
pre-1.0
|
|
|
|
# Next Step
|
|
|
|
Triage the stale remote branches (issue #71): for each, merge the work
|
|
or delete the branch.
|
|
|
|
# Completed Steps
|
|
|
|
- 2026-08-09: Adopted the remaining upstream `CHECK_EPOCH` hardening
|
|
(issue #91), closing the gap #85 knowingly left open. Four changes,
|
|
all four decided as adopt upstream in `sneak/prompts` #26. (1) Each
|
|
check stage now asserts `[ -n "$CHECK_EPOCH" ] || exit 1` before
|
|
running anything, so a build that supplies no `--build-arg` fails
|
|
instead of lying. This is the item that mattered: an unset `ARG` is
|
|
an empty string and an empty string is a stable cache key, so the
|
|
second and every later bare `docker build .` on an unchanged tree
|
|
replayed all three check layers and still exited 0 — and `docker
|
|
build .` is the command `REPO_POLICIES.md` names verbatim as a thing
|
|
that must be green, so the documented command was precisely the one
|
|
that lied. Failed steps are never cached, which is what makes the
|
|
guard fire on every invocation rather than once. (2) The epoch is now
|
|
expanded into each check command rather than left as a bare
|
|
declaration, so the cache miss no longer depends on BuildKit's
|
|
unreferenced-`ARG` handling staying as it is, and the value appears
|
|
in the build log. (3) `script/cibuild` uses
|
|
`epoch="$(date +%s%N)$$"`, unique per invocation rather than per
|
|
second; `%N` alone is insufficient because busybox drops it silently
|
|
and exits 0, and `$$` is what makes the guarantee hold regardless.
|
|
The bare-assignment form is kept deliberately — inlined in an
|
|
argument, a failing substitution does not abort under `set -eu` and
|
|
would yield an empty constant epoch, restoring the exact false green
|
|
being fixed. (4) `script/docker` passes the same fresh arg, so the
|
|
two entrypoints cannot disagree about whether the tree is green;
|
|
local builds are almost always warm, which made it the likelier
|
|
fooling in practice. The `ARG` placement from #85 is unchanged, below
|
|
`apk add`, `COPY go.mod go.sum` and `go mod download`, so dependency
|
|
layers still cache and the build is not cold. Verified by negative
|
|
control rather than inspection — a bare `docker build .` run twice
|
|
back to back, plus back-to-back pairs of both scripts and a host-side
|
|
`make check`; the measurements are recorded once, in the PR
|
|
verification comment, rather than restated here. `.golangci.yml`, the
|
|
lint-stage `FROM` line and its digest, `script/lint`,
|
|
`REPO_POLICIES.md` and `.gitea/workflows/check.yml` are all
|
|
untouched.
|
|
- 2026-08-09: Stopped `script/cibuild` from reporting a green it did
|
|
not earn (issue #85). A bare `docker build .` let Docker serve the
|
|
check layers from the layer cache whenever the tree had not changed:
|
|
the checks never executed and the build still exited 0. The fix is an
|
|
`ARG CHECK_EPOCH` declared immediately above the check `RUN`s in both
|
|
the lint stage and the builder stage (`ARG` scope is per-stage, so
|
|
each declares its own), with `script/cibuild` assigning
|
|
`epoch="$(date +%s)"` and passing `--build-arg CHECK_EPOCH="$epoch"`.
|
|
The assignment is separate on purpose: under `set -eu` a command
|
|
substitution that fails inside an argument does not abort the script,
|
|
which would leave an empty constant `CHECK_EPOCH` and restore the
|
|
very false green being fixed. Placement is the rest of the point —
|
|
the `ARG` sits below the `apk add`, `COPY go.mod go.sum`, and `go mod
|
|
download` layers, so only the checks are invalidated and the
|
|
dependency layers still cache. The guarantee is conditional on a
|
|
fresh value rather than absolute: a bare `docker build .` gets an
|
|
empty `CHECK_EPOCH` and can still serve the check layers from cache,
|
|
which `README.md` and the `Dockerfile` now say plainly, with issue
|
|
#91 tracking the upstream hardening (expanded `ARG` form, unset
|
|
guard, per-invocation epoch, `script/docker`) that would close it.
|
|
Verified by re-running the reproduction plus the withheld-`--build-arg`
|
|
counterfactual; the measurements are recorded once, in the PR #89
|
|
verification comment, rather than restated here. `.golangci.yml`, the
|
|
lint-stage `FROM` line and its digest, `script/lint`, and
|
|
`.gitea/workflows/check.yml` are all untouched.
|
|
- 2026-08-09: Corrected the `Vaultik.UI` doc comment (issue #84). It
|
|
claimed the cli layer replaces the writer with a discarding one in
|
|
`--cron` mode; the actual mechanism is `UI.SetQuiet(true)` in
|
|
`setupGlobals`, which drops Begin/Complete/Info/Notice/Detail/
|
|
Progress/Banner but still emits Warning and Error. The `--cron` line
|
|
in `README.md` said "Silent unless error", which understated what
|
|
survives, and now names warnings too. The other `--cron` comments
|
|
(`internal/log/log.go`, `internal/cli/snapshot.go`,
|
|
`internal/vaultik/snapshot.go`) were audited and already accurate.
|
|
Comments and docs only, no behavior change.
|
|
- 2026-08-09: Made `snapshot list` list the destination store without
|
|
the private key (issue #64). The listing is now the union of the
|
|
local index and a single streamed listing of the `metadata/` prefix,
|
|
with no `age_secret_key` gate — the manifest is unencrypted, so a
|
|
host holding only the public key can enumerate its own backups and a
|
|
host that lost its local index can still see them. A remote-only
|
|
snapshot's hostname and name are deliberately not recovered (they are
|
|
not recoverable without the private key, and making them so would
|
|
undo the privacy property tracked in issue #81); such rows are
|
|
labelled by an abbreviation of their remote key and carry the real
|
|
timestamp and compressed size from the manifest, with `<remote only>`
|
|
in the two columns that require the local index. Local-only snapshots
|
|
are reported as drift, and the hint now names `vaultik prune`, which
|
|
exists, instead of `vaultik snapshot cleanup`, which does not.
|
|
`reportRemoteDrift` collapsed into the merged view. Every remote
|
|
manifest read in the codebase now goes through
|
|
`downloadManifestByKey`, so issue #81 has one call site to change.
|
|
Review rework: snapshot timestamps now normalize to UTC in
|
|
`scanSnapshotRows`, the one place they enter the domain, so the merged
|
|
TIMESTAMP column cannot show local time for a locally tracked row and
|
|
UTC for a remote-only row on a non-UTC host; `GetIncompleteByHostname`
|
|
was folded onto that same scanner. `--json` now reports the
|
|
unreadable-manifest count and the 1000-row truncation on stderr
|
|
instead of returning a silently short document (the document's shape
|
|
is unchanged). The two per-snapshot `log.Warn` calls on the listing
|
|
path now route through the same JSON-aware writer as the existing
|
|
workaround, so one corrupt manifest can no longer put a log line on
|
|
stdout ahead of the document and break `| jq` — still a local
|
|
workaround pending issue #82. Verified with `script/cibuild` and with
|
|
an uncached `make check` (`0 issues.`, no cached test packages), plus
|
|
end to end against a `file://` destination with no secret key present.
|
|
- 2026-08-09: Closed the gap between `make lint` and CI (issue #78).
|
|
`script/lint` now runs the digest-pinned `golangci-lint` image taken
|
|
from the `Dockerfile` lint stage, which is the single source of truth
|
|
for the linter version; the duplicate pin in the `Makefile` `deps`
|
|
target and the unpinned `golangci-lint` install in `script/bootstrap`
|
|
are gone. A `golangci-lint` on `PATH` is used only when its version is
|
|
exactly the pinned one (which is how the lint stage runs it inside the
|
|
container); anything else goes through Docker, and a missing or
|
|
unreachable Docker daemon is a hard error rather than a silent
|
|
fallback. `make check` is therefore now as trustworthy as
|
|
`script/cibuild`.
|
|
- 2026-08-09: Finished the lint remediation under the canonical
|
|
`.golangci.yml` (issue #61, which also unblocks issue #59). The
|
|
remaining findings were fixed behavior-preservingly: `wsl_v5`
|
|
whitespace, `sqlclosecheck`, and `prealloc`. The `sqlclosecheck` sites
|
|
now close `sql.Rows` in a deferred closure instead of via the
|
|
`CloseRows` helper, which the linter could not see through. Only the
|
|
`revive` package-name findings remain suppressed, with per-site
|
|
`//nolint` directives; the package-rename question behind them is
|
|
tracked in issue #76. Verified with `script/cibuild`, which exits 0 —
|
|
that is the only trustworthy gate, because `script/lint` runs whatever
|
|
`golangci-lint` happens to be on `PATH` rather than the pinned
|
|
v2.12.2 that CI and the `Dockerfile` use, so `make check` can report
|
|
green on findings CI still fails. That tooling gap is tracked in issue
|
|
#78.
|
|
- 2026-08-09: The earlier next step "reconcile the uncommitted
|
|
`ARCHITECTURE.md` edits on `main`" needed no work: the working tree is
|
|
clean and `ARCHITECTURE.md` is committed on `main`.
|
|
- 2026-08-07: Updated golangci-lint to v2.12.2 everywhere it is pinned
|
|
(`Dockerfile` lint stage, `Makefile` deps target), replaced
|
|
`.golangci.yml` with the canonical config (v2 schema, `default: all`),
|
|
and remediated the bulk of the lint findings it surfaced (issue #61):
|
|
behavior-preserving fixes across every package, 2,990 findings down to
|
|
80. `make test` and `make fmt-check` were green at that point but
|
|
`make lint` was still red; the commit message claiming `make check`
|
|
was green was wrong.
|
|
- 2026-08-07: Added the standard `.golangci.yml` and `.editorconfig`
|
|
(issue #59); lint findings under the new config are tracked in issue
|
|
#61. `script/bootstrap` now installs sqlite3 (needed by tests).
|
|
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints,
|
|
Makefile shims, README Entrypoints section
|
|
- 2026-07-02: Consolidated CLI verbs, retired overlapping commands; bound
|
|
the local index to its backup destination URL.
|
|
- 2026-06-28: snapshot rm now removes metadata only and prints the prune
|
|
command; restore skips chown when running as non-root.
|
|
- 2026-06-26: Snapshot IDs hashed at the storage boundary; snapshot list
|
|
made resilient to bad remote entries.
|
|
- 2026-06-24: Collapsed snapshot prune into vaultik prune; restore streams
|
|
blobs to disk and restores files in blob-locality order; cron output
|
|
fixes.
|
|
- 2026-06-17: Restore overhaul: ReadAt chunk reads from cached blobs,
|
|
reference-counted blob sweeper, integration tests; new internal/ui
|
|
output layer, banner, and progress lines.
|
|
- 2025-12-18: Added ARCHITECTURE.md and godoc coverage for exported API.
|
|
- 2025-07-26: End-to-end integration tests; manifest format refactor;
|
|
renamed backup to snapshot; afero filesystem abstraction.
|
|
- 2025-07-20: Initial design and implementation: cobra + fx CLI skeleton,
|
|
SQLite index database, UUID blob storage with streaming chunking.
|
|
|
|
# Future Steps
|
|
|
|
- Define remaining scope for a first tagged release and cut v0.1.0.
|