Files
vaultik/TODO.md
sneak 9a45221b79
All checks were successful
check / check (pull_request) Successful in 2m24s
Fix timezone drift and --json truncation in snapshot list (closes #64)
Three defects in the merged listing path, all found in review.

1. The TIMESTAMP column mixed two timezones. Remote-only rows render
   timestamp.UTC(); local rows come from ListRecent, whose scanner
   decoded Unix seconds in the host's local zone, unlike the two other
   snapshot scanners in that file. Both render through the same
   zone-less format string, so on a non-UTC host the same snapshot
   showed one time when locally tracked and another when remote-only,
   in the same column, with nothing to indicate why.

   Normalized at the point the timestamp enters the domain rather than
   at the display layer: scanSnapshotRows now decodes in UTC like its
   siblings, and GetIncompleteByHostname's copy of that loop was folded
   onto the shared scanner so the three call sites cannot drift apart
   again.

2. --json silently truncated. The early return skipped reportListDrift,
   so neither the 1000-row cap nor the unreadable-manifest count reached
   a machine consumer: past the cap the document was short with no
   signal at all. Both counts now go to stderr, where the
   unreachable-destination warning already goes. The document's shape is
   deliberately unchanged, so existing consumers keep parsing.

3. The --json stderr workaround was half-applied. Two per-snapshot
   log.Warn calls on the same new path were left unguarded, and the
   logger writes to stdout at default level, so one corrupt manifest put
   a log line ahead of the document and broke `| jq`. Both now route
   through the same JSON-aware writer. They are also collected during
   the concurrent manifest reads and emitted afterwards in key order,
   since that writer is not safe for concurrent use. Still a local
   workaround; the logger itself is issue #82.

Also from review, non-blocking: the destination-listing failure is no
longer printed twice in table mode, the identifier cell is no longer
computed and discarded for locally tracked rows, and the merge sort is
stable so that rows sharing the zero-timestamp fallback keep a
deterministic order.

Tests: each fix has a test that fails without it. The timezone tests
pin time.Local to a non-UTC zone and assert Location identity, so they
would have caught this on the UTC host where it was missed. The JSON
stdout test redirects the process's own stdout to a pipe and rebuilds
the logger over it, so it can actually observe a log line landing on
stdout ahead of the document rather than asserting on an injected
buffer the log never reaches.
2026-08-09 05:21:43 +00:00

6.2 KiB

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: 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.