Skip to content

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”

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 labelled needs-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.

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_human expanded from {reason} to {reason, source?, evidence_url?}. source is an enum: "github_label" | "decision_doc" | "ci_failure". Backward- compatible: reason remains the only required field.
  • ci_status added as a new optional top-level field: {conclusion, workflow, run_url, updated_at} | null. conclusion maps the GitHub Actions conclusion/status enum to an eight-value subset. additionalProperties: false at 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) — calls GET /repos/{owner}/{repo}/issues?state=open&labels=needs-human&per_page=1. Returns the first match as {reason, source:"github_label", evidence_url} or null. Non-fatal on API error (returns null; sweep continues).
  • fetchCiStatus(url, branch) — calls GET /repos/{owner}/{repo}/actions/runs?branch={branch}&per_page=1. Maps GitHub’s conclusion/status to the schema’s conclusion enum. Returns null when 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: summariseStatus now 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_status is non-null the pill links to run_url. Glyphs: ✓ success, ✗ failure/timed_out/action_required, … pending, ? null/unknown.
  • needs_human chip: when evidence_url is 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 to needs_human > gate_blocked > ci_failure > unreachable/degraded/other > ci_pending > integration_pending > ok > na.
  • Decision doc body parsing for needs_human: the taskset spec mentioned parsing a needs_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-repo workflow_name config in registry.yaml was 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.
  • petrova sweep-state writes needs_human and ci_status fields for at least two repos with non-null values (requires GITHUB_TOKEN and a repo with a needs-human label or a recent Actions run).
  • /console cards show a CI status pill (✓/✗/…/?) for every repo.
  • /console cards show a linked needs 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-existing diagnose test failure is unrelated and predates TASKSET 1).
  • cd dashboard && npx astro check → 0 errors.
  • 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