Skip to content

feat(amico-run): amico fleet digest — the fleet's Slack projection verb (unified-fleet slice 1) #428

Description

@aarontrowbridge

Important

Problem

The fleet has three renderings (CLI, dashboard widget, in-chat /fleet) and no team-visible one — "how is the fleet doing?" requires SSH. The unified-fleet spec (spec-20260818-150711, slice 1) makes Slack the fourth rendering; this issue is the product artifact that renders it.

Approach

A fleet digest subcommand in @amicode/amico-run: a pure formatter over the existing fleet registry plus configured machine probes, posting through the amico-slack CLI as a subprocess contract (no Slack API code in amico-run). Key reason: the shipped artifact is the verb, not the job — Notturno, cron, and on-demand are interchangeable triggers.

Approaches Considered

  • Slack API client inside amico-run — rejected: a second Slack client; the CLI already owns posting, voice, and footer logic.
  • Digest logic living in the Notturno workflow — rejected: Harmoniqs-only, not product.
  • Machine auto-discovery from ~/.ssh/config / tailscale — rejected: fleet topology would leak into product code; machines come from config.
  • (chosen) Formatter verb + subprocess posting via amico-slack.

Scope

In: the digest subcommand; machine probing from --machines / AMICO_FLEET_MACHINES; sessions summary from the fleet registry; --post channel resolution; full vitest suite.
Out: the Node amico-slack bin port (separate issue — this verb only needs the CLI on PATH); the org projection (slice 3, gated on spec #318 Phase 2); any scheduler; "last seen" timestamps for down machines (needs state, v2).

Assumptions / Open Qs

amico-slack exists on PATH where the digest runs (at Harmoniqs: the mini, the fleet server). The Notturno wrapper injects its jobs rollup via an optional --jobs-line string so the product verb stays free of Notturno coupling.


Acceptance Criteria

  1. amico fleet digest defaults to dry-run: prints the ≤6-line distilled block and the full table, exit 0.
  2. Machines resolve --machines a,bAMICO_FLEET_MACHINES env → honest "n/a" line when unconfigured; an unreachable machine renders and NEVER fails the digest (degrade-graceful).
  3. Sessions render from the fleet registry (--root respected like the other fleet verbs); an empty registry renders "0 live sessions" honestly.
  4. --post <channel> (or AMICO_SLACK_FLEET_CHANNEL) posts the block via amico-slack send <ch> --file, parses the emitted ts, then posts the table --thread <ts>.
  5. amico-slack absent from PATH → errors-as-data (ok:false, exit 64), never a stack trace.
  6. No channel names, hostnames, or Harmoniqs topology in the module — machines and channel are config.
  7. pnpm --filter @amicode/amico-run test fleet_digest green; typecheck green.

Key Decisions

  • Posting is a subprocess contract: shell amico-slack, parse sent → … ts=<TS> from stdout for the thread reply. Keeps zero Slack deps in amico-run.
  • Registry read reuses readAllRecords from the fleet registry module — the digest never recomputes state (the projection invariant).
  • Machine probe: ssh -o BatchMode=yes -o ConnectTimeout=6 <alias> 'echo ok; hostname' — probe function injectable for tests.
  • Block posts even if the table post fails: the digest going out outranks the thread reply (ok:true + warning).
  • Optional --jobs-line "<text>" renders as the third block line — the Notturno wrapper supplies it; product users omit it.

Constraints & Invariants

  • Slack is a projection: the digest READS fleet verbs/registry and never writes a record.
  • The voice contract rides the CLI (app posts carry no footer); the verb adds none of its own.
Prior Art / Patterns
  • packages/amico-run/src/fleet_verb.ts — subcommand routing + errors-as-data idiom; header doctrine ("every fleet surface reads through THESE verbs").
  • packages/amico-run/src/fleet_registry.tsreadAllRecords, fleetRoot, purity-boundary house style.
  • packages/amico-run/test/fleet_verb.test.ts — temp-root test idiom.
  • ~/.local/bin/amico-slack — the posting contract (send <ch> --file <f> [--thread <ts>], sent → … ts=).

Source

  • Durable record: specs/spec-20260818-150711-unified-amico-fleet-slack.md (personal vault, rev 2, commit 791aa4a) — slice 1 product half.
  • Companion issue (harmoniqs/amico): the Notturno trigger + alert thread-dedup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    hitlHuman-in-the-loop — needs human review/decision

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions