Skip to content

`last_full_sweep` is a liveness heartbeat; stale threshold drops to 2h

Date: 2026-08-05 Status: closed Supersedes: none Superseded-by: none — current

The petrova.host console reported 39 of 39 registered repos as stale-data, every card showing no integration data and swept — stale. Investigation established the sweep was alive and healthy the entire time: run 31017673968 completed success at 2026-08-05T14:55Z in 58s and resolved real data (scanned=39 changed=4 skipped=4). The last commit that actually landed state was 3eca387 on 2026-07-09 — 27 days of green runs producing zero writes.

Three defects combined:

  1. sweepOne (cli/src/verbs/sweep_state.ts) returned early when the six semantic fields matched the prior file, skipping the writeFileSync. last_full_sweep was computed into the merged object and discarded. The field only ever advanced when something else changed — so it recorded “last time this repo moved”, while every consumer read it as “last time we looked”.
  2. The workflow’s commit gate used git diff --quiet -- state/, which does not see untracked files. The four repos with no state file on main (yao-agent, arno-host, bhava-blue, mary-wiki) were written into the runner workspace and discarded every 30 minutes.
  3. Repos whose signals all resolve null never got a state file at all: with no prior file the before/after comparison matched trivially and the early return fired. asgard, abacus, fathom, templates sat in this hole — neither changed nor skipped, simply invisible.

Separately, the stale threshold was defined twice and inconsistently: 3 days in dashboard/src/lib/fleet-state.ts (citing spec §2) and 24h in host/src/tools/sweeps.ts. Spec §2 justifies 3 days on the premise that “the state-sweep cron cadence is daily”. The cadence is and has been */30 * * * *.

last_full_sweep is redefined as the fleet’s liveness heartbeat: it is written on every successful sweep pass, whether or not anything changed. The changed flag becomes report-only and no longer gates the write. Skip paths (no url in registry, fetch failed: …) still write nothing, so a GitHub outage cannot stamp a repo as freshly swept.

This makes state/ dirty on every run. That is accepted: the sweep commits every run (~48/day, [skip ci], single-line diffs), and the commit log becomes the sweep’s audit trail. The timestamp is deliberately not rounded or debounced — doing so would reintroduce a resolution gap below the cron cadence.

STALE_THRESHOLD_MS is unified at 2 hours in a single definition (host/src/freshness.ts), consumed by both the MCP tool and the console. 2h is a tolerance on the 30-minute cadence that absorbs GitHub’s observed scheduling deferrals (real gaps in the run history run ~2-3h) while surfacing a dead sweep the same day. This supersedes the 3-day figure in spec §2.

  • Round last_full_sweep to the hour so identical-content runs produce no diff — cuts commit volume ~16x but degrades heartbeat resolution below the 30-minute cadence, which is the precise signal being restored.
  • Write the file but keep committing only on semantic change — keeps state/ history clean and reintroduces the exact bug: main would still carry a frozen timestamp.
  • Add a separate heartbeat field and leave last_full_sweep alone — a second freshness field every consumer must learn about, when the existing one was simply being written at the wrong time.
  • Keep 3 days / adopt 24h fleet-wide — both are forgiving enough that a fully stalled cron hides for a day or more, which is the failure this signal exists to catch.

For code:

  • cli/src/verbs/sweep_state.ts — write on every non-skipped row; changed reports semantic movement only; role: placeholder entries get a distinct skip reason instead of reading as failures.
  • .github/workflows/state-sweep.yml — commit gate stages first (git add -A state/) then tests git diff --cached --quiet, so new files are seen.
  • cli/src/github-client.tsparseRepoUrl accepts dots in the repo name (devaqua.blue previously threw PARSE_REPO_URL), still rejecting deep paths.
  • cli/src/types.tsRepoRole gains placeholder, which registry.schema.json has carried since the thrustr-io/downlink-hq/pwplz entries landed; the type had drifted behind the schema.
  • host/src/freshness.ts (new) — sole definition of STALE_THRESHOLD_MS.
  • host/src/tools/sweeps.ts, dashboard/src/lib/fleet-state.ts — consume it.

For docs:

  • docs/superpowers/specs/2026-05-16-fleet-lifecycle-verdict-design.md §2 gains a supersession pointer to this doc. The original text is left intact (MR-7).

For in-flight phases:

  • None. The control plane’s self-entry carries current_phase: null.

For invariants:

  • No MR-N additions, modifications, or repeals.
  • Spec: docs/superpowers/specs/2026-05-16-fleet-lifecycle-verdict-design.md §2 (the superseded 3-day threshold and its “cadence is daily” premise).
  • Workflow run 31017673968 — green sweep, scanned=39 changed=4 skipped=4, No state changes to commit.
  • Commit 3eca387 (2026-07-09) — last sweep commit prior to this fix.
  • MR-7 — supersession via new dated doc, never silent edits.
  • Subagent: state-sweep rectification (this session)
  • Human: alex@devarno.com — 2026-08-05