TASKSET 1 — sweeper-derived `needs_human` and `ci_status` signals
date: 2026-05-10 slug: derived-needs-human-and-ci-status status: open mr: [MR-1, MR-7, MR-10] outranks: finding
Section titled “date: 2026-05-10 slug: derived-needs-human-and-ci-status status: open mr: [MR-1, MR-7, MR-10] outranks: finding”TASKSET 1 — sweeper-derived needs_human and ci_status signals
Section titled “TASKSET 1 — sweeper-derived needs_human and ci_status signals”Context
Section titled “Context”The /console fleet overview dashboard rendered “unknown” for two operator-glance
signals that can be derived entirely from public GitHub state:
needs_human— the schema field existed but was never populated. The sweep made no GitHub API calls to detect open issues or PRs labelledneeds-human.ci_status— the field did not exist anywhere in the data model (schema, host types, dashboard types).
Additionally, dashboard/src/pages/console/index.astro’s summariseStatus function
counted "verified" and "consistent" string values against integration statuses, but
contracts/state.schema.json only allows ok, degraded, failing, stale,
not_applicable, pending. This vocabulary mismatch caused the ok pill to never
render even when integrations were passing.
Decision
Section titled “Decision”Sweeper-first (architectural principle): both signals are derived from public GitHub state fetched at sweep time, not via new MCP endpoints or per-user credentials. The state files remain the single source of truth for the dashboard’s read path.
Schema changes (contracts/state.schema.json)
Section titled “Schema changes (contracts/state.schema.json)”needs_humanexpanded from{reason}to{reason, source?, evidence_url?}.sourceis an enum:"github_label" | "decision_doc" | "ci_failure". Backward- compatible:reasonremains the only required field.ci_statusadded as a new optional top-level field:{conclusion, workflow, run_url, updated_at} | null.conclusionmaps the GitHub Actions conclusion/status enum to an eight-value subset.additionalProperties: falseat the schema root means both fields are explicitly registered.
Sweep changes (cli/src/remote-walker.ts, cli/src/verbs/sweep_state.ts)
Section titled “Sweep changes (cli/src/remote-walker.ts, cli/src/verbs/sweep_state.ts)”Two new exported functions in remote-walker.ts:
fetchNeedsHumanSignal(url, branch)— callsGET /repos/{owner}/{repo}/issues?state=open&labels=needs-human&per_page=1. Returns the first match as{reason, source:"github_label", evidence_url}ornull. Non-fatal on API error (returnsnull; sweep continues).fetchCiStatus(url, branch)— callsGET /repos/{owner}/{repo}/actions/runs?branch={branch}&per_page=1. Maps GitHub’sconclusion/statusto the schema’sconclusionenum. Returnsnullwhen no runs are found or the fetch fails.
Both functions are called via Promise.all inside sweepOne alongside the existing
walkDecisionsRemote and fetchFile calls, adding ~2 API calls per repo per sweep run.
At 11 repos, unauthenticated rate limit (60 req/h) is tight; GITHUB_TOKEN is strongly
recommended for scheduled sweeps (5 000 req/h authenticated).
SweepStateRow extended with needs_human and ci_status; change detection updated
to include both signals so state files are only rewritten when these values change.
Host changes (host/src/sources/petrova.ts)
Section titled “Host changes (host/src/sources/petrova.ts)”NeedsHuman interface expanded with optional source and evidence_url fields.
New CiStatus interface added. RepoState gains an optional ci_status?: CiStatus | null
field. No runtime logic change — loadState is a raw YAML parse; the new fields are
present in YAML after a sweep run.
Dashboard changes (dashboard/src/pages/console/index.astro)
Section titled “Dashboard changes (dashboard/src/pages/console/index.astro)”- Vocab fix:
summariseStatusnow maps"ok"→summary.ok,"degraded"→summary.degraded,"failing"/"stale"/"unreachable"→summary.unreachable. The former"verified"/"consistent"string comparisons are removed; they never matched valid schema values. - CI pill: each card renders a CI status pill. When
ci_statusis non-null the pill links torun_url. Glyphs: ✓ success, ✗ failure/timed_out/action_required, … pending, ? null/unknown. needs_humanchip: whenevidence_urlis present the chip renders as an anchor (<a>) to the source issue or PR. When absent it renders as a plain span (unchanged from before).cardHealth: priority updated toneeds_human > gate_blocked > ci_failure > unreachable/degraded/other > ci_pending > integration_pending > ok > na.
Alternatives considered
Section titled “Alternatives considered”- Decision doc body parsing for
needs_human: the taskset spec mentioned parsing aneeds_human:YAML block from the latest decision doc content. Deferred — fetching and parsing file contents for every decision doc adds latency and complexity for a signal that operators can also express via GitHub labels. Labels are lower friction. - Named workflow selection for
ci_status: per-repoworkflow_nameconfig inregistry.yamlwas considered. Deferred — adds a registry schema change and per-repo config overhead for marginal accuracy gain. The most-recent run on the default branch is sufficient for a fleet-overview glance.
Verification gates (MR-10)
Section titled “Verification gates (MR-10)”-
petrova sweep-statewritesneeds_humanandci_statusfields for at least two repos with non-null values (requiresGITHUB_TOKENand a repo with aneeds-humanlabel or a recent Actions run). -
/consolecards show a CI status pill (✓/✗/…/?) for every repo. -
/consolecards show a linkedneeds human: <reason>chip when the field is set. - State-sweep cron runs unchanged (no new required config).
-
cd host && npm test→ 74 passed. -
cd cli && npm test→ 185 passed (pre-existingdiagnosetest failure is unrelated and predates TASKSET 1). -
cd dashboard && npx astro check→ 0 errors.
References
Section titled “References”- TASKSET 1 scope: implementation-strategy document (2026-05-10)
- Sweeper-first principle: same document, Architectural principles section
- MR-7: decision docs are dated and append-only
- MR-10: verification rounds are mandatory at phase close