Files
harmony/harmony-reconciler-contracts
Jean-Gabriel Gill-Couture 0793e72a05
All checks were successful
Run Check Script / check (pull_request) Successful in 2m30s
feat(fleet-deploy): log-tail contract as a Score companion
Third Score companion (after AgentObservation and SmokeTest), per
ADR-023 P7 — new framework capabilities attach as companions rather
than as additions to the Score / Interpret public API. Powers the
dashboard's "View logs" UX: customer clicks the button, gets the last
N lines of a deployment's container output from the device.

Trait + transport-side impl + unit tests ship now. The agent-side
Verb::Logs handler and the operator dashboard handler land in
follow-up PRs against the contract locked here — splitting keeps each
diff focused and reviewable.

Three small types + a NATS-backed impl, zero edits to Score /
Interpret / Maestro:

  LogChunk          pure value: source identifier, captured_at, lines
                    (oldest-first), truncated flag. No transport,
                    no async, no IO — the dashboard renders it,
                    the transport layer constructs it.
  LogQueryError     six arms, each mapped to a distinct operator
                    action (DeviceOffline vs Timeout vs Agent vs
                    BadReply vs Transport vs InvalidReply). Mirrors
                    the FleetCommandsClient::CommandError shape used
                    by Verb::Ping so callers see uniform error
                    surfaces across verbs.
  LogQuery<T>       companion trait paired with a Score by associated
                    type — Q: LogQuery<T, Score = S> is the same
                    compile-time lock SmokeTest uses. A future
                    K8sLogQuery follows the same shape, no
                    Box<dyn LogQuery> needed (topologies are
                    compile-time per ADR-023 P6).
  PodmanLogQuery    NATS request/reply impl targeting
                    device-commands.<id>.logs. Splits routing
                    (LogQueryRouting) from transport so unit tests
                    verify the exact wire bytes without a NATS
                    client. Saturates LogsRequest.lines at
                    LOGS_MAX_LINES on the operator side as
                    defense-in-depth (the agent will clamp again).

reconciler-contracts gains Verb::Logs, LogsRequest, LogsReply, and
the LOGS_MAX_LINES bound. The wire shape lives there (not in the
deploy crate) so the agent build — which must not depend on harmony
— can serialize the same bytes. Adding the verb required zero
permission template changes: the agent's existing
device-commands.<id>.> subscription already covers it, and the
verb stays the trailing subject token so Verb::as_subject_token
keeps its invariant.

Tests assert behavior, not shape: subject_matches_documented_format
locks the wire so a callout permission change can't silently break
routing, request_body_clamps_oversized_n proves the
buggy-dashboard-show-all-button can't get through unchecked,
decode_reply_rejects_invalid_source_name proves a malicious agent
can't smuggle control characters past ProbeName validation, and
paired_score_type_is_podman_v0_score is a compile-time check that
catches refactors changing the associated type without updating
callers. 77 unit tests total across both crates, all passing without
requiring a real podman socket or NATS server.

Deferred (in scope of v0.3, separate PRs):
  - Agent-side Verb::Logs handler in command_server.rs (parses
    LogsRequest, resolves deployment->container with stricter
    [a-zA-Z0-9_.-]{1,128} validation, runs podman logs --tail,
    serializes LogsReply).
  - Operator dashboard handler at
    /deployments/<name>/devices/<id>/logs.
  - End-to-end integration test through a real podman container.
2026-05-26 07:06:58 -04:00
..