Stale-state accounting for the two Vis-owned directories that grow without
bound: the drafts store (~/.vis/drafts) and the gateway journals
(~/.vis/gateway/events).
Both are legitimately unbounded by design. A draft clone is a full copy of a
trunk and survives until someone applies or abandons it, so a machine that
drafts daily and never abandons accumulates gigabytes of dead clones. Gateway
journals self-sweep inside the tailer loop (gateway.bus/sweep!), but only
while a daemon is actually running — journals from crashed or never-restarted
daemons stay forever.
Neither is an error, so nothing here deletes on its own: scan is pure
observation (no mutation, never throws) that vis doctor renders, and
purge! is the explicit operator action behind vis doctor --purge.
purge! routes deletions through workspace/abandon! for live draft rows so
the DB transition, hooks, and backend root release all happen exactly as they
would from /draft abandon. Only rows already :discarded, directories with
no row at all, and journal files are removed directly — and every direct
delete is confined to a path under the drafts store or the events dir.
Stale-state accounting for the two Vis-owned directories that grow without bound: the drafts store (`~/.vis/drafts`) and the gateway journals (`~/.vis/gateway/events`). Both are legitimately unbounded by design. A draft clone is a full copy of a trunk and survives until someone applies or abandons it, so a machine that drafts daily and never abandons accumulates gigabytes of dead clones. Gateway journals self-sweep inside the tailer loop (`gateway.bus/sweep!`), but only while a daemon is actually running — journals from crashed or never-restarted daemons stay forever. Neither is an error, so nothing here deletes on its own: `scan` is pure observation (no mutation, never throws) that `vis doctor` renders, and `purge!` is the explicit operator action behind `vis doctor --purge`. `purge!` routes deletions through `workspace/abandon!` for live draft rows so the DB transition, hooks, and backend root release all happen exactly as they would from `/draft abandon`. Only rows already `:discarded`, directories with no row at all, and journal files are removed directly — and every direct delete is confined to a path under the drafts store or the events dir.
Test seam for the gateway journal directory. nil (production) resolves to
~/.vis/gateway/events, mirroring the private gateway.bus/events-dir —
journals are addressed by absolute path from several processes, so that
location is a fixed contract rather than a user-facing configurable.
Test seam for the gateway journal directory. `nil` (production) resolves to `~/.vis/gateway/events`, mirroring the private `gateway.bus/events-dir` — journals are addressed by absolute path from several processes, so that location is a fixed contract rather than a user-facing configurable.
Test seam for the diagnostic log directory. nil (production) resolves to
~/.vis/logs, mirroring internal.paths/logs-dir — the location is a
fixed contract shared with the sandbox grant, not a configurable.
Test seam for the diagnostic log directory. `nil` (production) resolves to `~/.vis/logs`, mirroring `internal.paths/logs-dir` — the location is a fixed contract shared with the sandbox grant, not a configurable.
Age past which a diagnostic log is deleted automatically. Three weeks: longer
than any plausible debugging window (a bug reported on Friday is still
readable the Monday after next), short enough that the directory stays
navigable and a grep over it does not walk a year of dead sessions.
Age past which a diagnostic log is deleted automatically. Three weeks: longer than any plausible debugging window (a bug reported on Friday is still readable the Monday after next), short enough that the directory stays navigable and a `grep` over it does not walk a year of dead sessions.
Age past which unattended state is worth mentioning. Two weeks: long enough that a draft parked over a holiday is not nagged about, short enough that the report still arrives while the operator remembers what the draft was for.
Age past which unattended state is worth mentioning. Two weeks: long enough that a draft parked over a holiday is not nagged about, short enough that the report still arrives while the operator remembers what the draft was for.
(format-bytes n)Human byte size. Locale-stable — explicit Locale.US so output is
deterministic across machines (no 253,1 MB from a comma-decimal locale).
Human byte size. Locale-stable — explicit Locale.US so output is deterministic across machines (no `253,1 MB` from a comma-decimal locale).
(purge! {:keys [db-info is-dry-run] :as opts})Reclaim everything scan reported. Returns the scan augmented with a
:purged vec (each item stamped :is-purged) and :reclaimed-bytes.
With :is-dry-run true nothing is touched: :purged still carries the plan
with every item stamped :is-purged false, so operators can look first.
Reclaim everything `scan` reported. Returns the scan augmented with a `:purged` vec (each item stamped `:is-purged`) and `:reclaimed-bytes`. With `:is-dry-run` true nothing is touched: `:purged` still carries the plan with every item stamped `:is-purged false`, so operators can look first.
(scan {:keys [db-info days now-ms]})Observe stale drafts and gateway journals. Pure: touches no state and never throws — a missing DB, an absent drafts store, or an unreadable subtree all degrade to empty findings.
Options: :db-info (nil is fine, drafts then reduce to on-disk orphans),
:days (defaults to default-stale-days) and :now-ms for tests.
Observe stale drafts and gateway journals. Pure: touches no state and never throws — a missing DB, an absent drafts store, or an unreadable subtree all degrade to empty findings. Options: `:db-info` (nil is fine, drafts then reduce to on-disk orphans), `:days` (defaults to `default-stale-days`) and `:now-ms` for tests.
(sweep-logs!)(sweep-logs! {:keys [days now-ms]})Delete every stale regular file directly under ~/.vis/logs. Returns
{:root :days :cutoff-ms :file-count :deleted :bytes} — :deleted counts
files actually removed and :bytes the space reclaimed.
Never throws and never recurses: only immediate children are considered, only
regular files (a symlink or subdirectory is left alone), and every candidate
is re-checked with under? against the canonical logs root so a hostile
symlinked entry cannot walk the delete out of the directory.
Options: :days (defaults to default-log-retention-days) and :now-ms for
tests.
Delete every stale regular file directly under `~/.vis/logs`. Returns
`{:root :days :cutoff-ms :file-count :deleted :bytes}` — `:deleted` counts
files actually removed and `:bytes` the space reclaimed.
Never throws and never recurses: only immediate children are considered, only
regular files (a symlink or subdirectory is left alone), and every candidate
is re-checked with `under?` against the canonical logs root so a hostile
symlinked entry cannot walk the delete out of the directory.
Options: `:days` (defaults to `default-log-retention-days`) and `:now-ms` for
tests.(sweep-logs-async!)(sweep-logs-async! opts)Fire-and-forget sweep-logs! on a lowest-priority daemon thread. Called once
per process at startup: hundreds of File stats are trivial but they are
still disk I/O on the path to first paint, and a sweep that loses the race
with a short-lived vis --version simply runs on the next start. Returns the
thread.
The body is a bound-fn so *logs-home* CONVEYS: a new thread otherwise
sees only root bindings, which would make a test's temp-dir binding silently
sweep the operator's real ~/.vis/logs. Production binds nothing, so the
conveyance is free.
Fire-and-forget `sweep-logs!` on a lowest-priority daemon thread. Called once per process at startup: hundreds of `File` stats are trivial but they are still disk I/O on the path to first paint, and a sweep that loses the race with a short-lived `vis --version` simply runs on the next start. Returns the thread. The body is a `bound-fn` so `*logs-home*` CONVEYS: a new thread otherwise sees only root bindings, which would make a test's temp-dir binding silently sweep the operator's real `~/.vis/logs`. Production binds nothing, so the conveyance is free.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |