Files
vaultik/TODO.md
sneak 24f6e2f9ef
All checks were successful
check / check (pull_request) Successful in 2m21s
Make script/cibuild unable to report an unearned green (closes #85)
script/cibuild was a bare `docker build .` with no cache control. The
Dockerfile does `COPY . .` and then `RUN make fmt-check` / `RUN make
lint` in the lint stage and `COPY . .` / `RUN make test` in the builder
stage. On an unchanged tree Docker served those RUN layers from cache,
so the checks never executed, and the build still exited 0 -- the exit
code, which is the one signal automation trusts, was wrong, and wrong
in the direction that matters: the longer a branch sits unchanged, the
more likely its "verification" is a replay, which is exactly its state
just before a merge.

Reproduced on this branch's base at 3bcdbcf. A changed-tree run took
162132ms and produced 14 `ok` lines and `0 issues.`; the immediately
following run, with nothing touched, took 221ms and produced 0 `ok`
lines and no `0 issues.` line at all, with 19 CACHED layers including
`RUN make fmt-check`, `RUN make lint`, and `RUN make test`. Both
exited 0.

The fix matches the upstream one in sneak/prompts #26 rather than
inventing a local variant: an `ARG CHECK_EPOCH` declared immediately
above the check RUNs, with script/cibuild passing a fresh
`--build-arg CHECK_EPOCH="$(date +%s)"` on every invocation. ARG scope
is per-stage in Docker, so the lint stage and the builder stage each
declare their own; covering only one would leave half the gate fake.

Placement is the substance of the change. The ARG sits below the
`apk add`, `COPY go.mod go.sum`, and `go mod download` layers in both
stages, so only the check layers are invalidated: earlier and every
build would be cold, later and the checks would stay cached. Confirmed
by measurement -- on a post-fix build every `apk add` and `go mod
download` layer is still reported CACHED, and a changed-tree build went
from 162132ms to 176221ms rather than to a cold build's 242727ms.

Verified against the original failure mode, not by trusting an exit
code: two back-to-back script/cibuild runs on an unchanged tree now
take 166745ms and 174025ms, each with 14 `ok` lines and `0 issues.`,
and neither reports CACHED on any of the three check layers.
Cross-checked host-side with `GOFLAGS=-count=1 make check`: exit 0, 14
`ok` lines, `0 issues.`, with no `parallel golangci-lint is running`
and no file paths from outside this worktree, so the lint result is a
real one and not a void or contaminated run.

.golangci.yml is unchanged (sha256 021cc83f4e6f...643346bcb), as is the
lint-stage FROM line that is the single source of truth for the linter
version, script/lint's pinned-image logic, and
.gitea/workflows/check.yml, whose only step is script/cibuild.
2026-08-09 06:08:33 +00:00

148 lines
8.3 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: 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. Reproduced on
this branch's base — a genuine changed-tree run took 162s with 14
`ok` lines, and the immediately following unchanged-tree run took
221ms with 0 `ok` lines, 19 cached layers, and the same exit 0, with
`RUN make fmt-check`, `RUN make lint`, and `RUN make test` all
reported `CACHED`. The fix matches the upstream one in
`sneak/prompts` #26: 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`
passing `--build-arg CHECK_EPOCH="$(date +%s)"`. Placement is the
whole 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. Verified by
re-running the reproduction: two back-to-back runs on an unchanged
tree took 167s and 174s, each with 14 `ok` lines and `0 issues.`, no
`CACHED` on any of the three check layers, while every `apk add` and
`go mod download` layer stayed `CACHED` in both. A changed-tree build
went from 162s to 176s, so this is not a cold build. `.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.