Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/reference/mcp-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,23 @@ To continue a stream approximately, call again with `since` set to the last
event's `ts`. `since` selects the Loki backend and cannot be combined with
`compose`. Expect overlap at the boundary and remove duplicates on the client.

## Container restarts

A restart of the logged container does not change the contract. A tail that
spans the restart still ends in one outcome within its bound. Compose may
reattach to the restarted container from a coarse timestamp, so a tail that
spans the restart can repeat a few lines from before it. A snapshot taken after
the restart holds each line once, in order, and a new tail starts from the
current output.

## Verification

`tests/mcp-logs-contract.test.ts` checks each outcome against a scripted stub
backend. The `mcp-logs-compose` scenario in the optional `docker-e2e` CI job
checks the same outcomes through `hack mcp serve` against a real Compose
project (see `tests/e2e/README.md`). It does not cover the Loki backend or the
native/authored logs path.

## Decision: no handles, cursors or idempotency keys (no-go)

Status: no-go for now (HACK-1134). This decision adds no cursor implementation.
Expand Down
8 changes: 4 additions & 4 deletions plugins/hack/skills/hack-cli/references/patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,13 @@ missing part with ad hoc scripts.
## HACK-P11: Stable agent guidance and typed operation results

- Use when: Agents run project workflows and need versioned guidance plus machine-readable results.
- Disposition: Partial. Missing: A guidance freeze during execution and a typed terminal result for resumed background operations remain open.
- Disposition: Partial. Missing: A freeze that refuses explicit guidance updates while an agent runs, and a typed terminal result for resumed background operations, remain open.
- Owner: WU19/WU21; tracked in HACK-1155.
- Commands: `hack up --json`, `hack down --json`, `hack restart --json`, `hack doctor --json`, `hack run <service> -- <cmd...>`
- Prerequisites: Generated guidance from Hack's canonical source with a content revision; `--json` on lifecycle and diagnostic commands.
- Env ownership: Results report names and states, not values.
- Readiness: Pass child argv after `--` in `hack run <service> -- <cmd...>`. Check the exit status or `--json` envelope directly rather than through a pipe filter.
- Cleanup: Guidance updates are explicit `hack setup` operations, never side effects of ordinary commands.
- Readiness: Pass child argv after `--` in `hack run <service> -- <cmd...>`; its exit status, and the MCP run result's `exitCode`, are the child's own. Check the exit status or `--json` envelope directly rather than through a pipe filter.
- Cleanup: Guidance updates are explicit `hack setup` operations, never side effects of ordinary commands. The `agent-surface-freeze` E2E scenario checks that init, agent, doctor and setup-sync runs leave tracked guidance and client config byte-identical.
- Provenance: 2 consumers (HACK-P11 research register).

<!-- negative-example -->
Expand All @@ -214,4 +214,4 @@ missing part with ad hoc scripts.

---

Content revision: `ff1faf16f047`. Generated by Hack; edit the canonical source and regenerate the plugin.
Content revision: `03a667cfa6a4`. Generated by Hack; edit the canonical source and regenerate the plugin.
6 changes: 3 additions & 3 deletions src/agents/pattern-catalogue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,14 +361,14 @@ export const PATTERN_CATALOGUE: readonly PatternCard[] = [
"Generated guidance from Hack's canonical source with a content revision; `--json` on lifecycle and diagnostic commands.",
envOwnership: "Results report names and states, not values.",
readiness:
"Pass child argv after `--` in `hack run <service> -- <cmd...>`. Check the exit status or `--json` envelope directly rather than through a pipe filter.",
"Pass child argv after `--` in `hack run <service> -- <cmd...>`; its exit status, and the MCP run result's `exitCode`, are the child's own. Check the exit status or `--json` envelope directly rather than through a pipe filter.",
cleanup:
"Guidance updates are explicit `hack setup` operations, never side effects of ordinary commands.",
"Guidance updates are explicit `hack setup` operations, never side effects of ordinary commands. The `agent-surface-freeze` E2E scenario checks that init, agent, doctor and setup-sync runs leave tracked guidance and client config byte-identical.",
negativeControl:
"Filtered shell text can hide a failed child, and historical dispatch, PR or gateway requests must not silently expand current product scope.",
domainOwner: "WU19/WU21",
missingContract:
"A guidance freeze during execution and a typed terminal result for resumed background operations remain open.",
"A freeze that refuses explicit guidance updates while an agent runs, and a typed terminal result for resumed background operations, remain open.",
provenance: { consumers: 2, source: "HACK-P11 research register" },
example: null,
e2eScenario: null,
Expand Down
43 changes: 43 additions & 0 deletions tests/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,49 @@ fixture; host and provider proxy settings remain unchanged. In a real project,
add its internal service names to the existing proxy exceptions rather than
discarding the project's other entries.

## MCP logs on real Compose (F13)

`mcp-logs-compose` (HACK-1134, HACK-1155) starts `hack mcp serve` over stdio
and calls the logs tools against a real Compose project. Every tool call runs
the CLI under test through `HACK_MCP_COMMAND`. The fixture uses cached
`alpine:3.20` with `pull_policy: never`, a private internal network, no
published ports, and Hack internal DNS/TLS turned off. Both log backends are set
to `compose`. It has three services: `ticker` prints a boot marker and then a
numbered line every 0.2 s, `quiet` prints one line, and `bulk` prints 1500
lines of 2048 four-byte emoji (about 12 MiB) and then idles.

The scenario requires these results:

- A `quiet` snapshot completes with `cutoff: null`. A `quiet` tail stops at
`maxMs` with a `time` cutoff and only its one line.
- A `ticker` tail stops at `maxMs` with a `time` cutoff and live lines in
order. Another stops at exactly 5 log events with an `events` cutoff.
- A `bulk` snapshot stops at 8 MiB with a `bytes` cutoff and
`outputTruncated`. It keeps an ordered prefix of whole lines. No result
payload may contain U+FFFD, so a split code point fails the run. Where the
cut falls varies from run to run, so a single pass does not prove that this
run cut inside a code point; `tests/mcp-logs-contract.test.ts` covers that
case deterministically.
- With `DOCKER_HOST` pointing at a missing socket, a snapshot and a tail both
return the closed `backend_error`. The tail ends at end of stream, not at
`maxMs`.
- `docker restart` on the exact owned `ticker` container during a tail: the
tail still ends in one typed outcome within its bound. Lines repeated across
the restart are logged, not asserted (see `docs/reference/mcp-logs.md`). The
stored history then holds `BOOT 1` and `BOOT 2` once each, with ordered
lines. A new tail streams only the new boot. A new MCP session does not
replay lines older than the previous session's last line.
- `hack run quiet -- false` exits 1, and an `exit 7` child exits 7 with its
output. The MCP `hack.project.run` result reports `ok: false` and
`exitCode: 7`, and a `true` child reports `ok: true`.

Cleanup removes only containers, networks and volumes with the fixture's
owner label.

```sh
HACK_E2E_CLI_BIN=./dist/hack HACK_E2E_DOCKER=1 HACK_E2E_REQUIRE_DOCKER=1 bun tests/e2e/run.ts --only=mcp-logs-compose
```

## Native file qualification

`native-config-files` requires the current compiled CLI, its matching compiler and
Expand Down
2 changes: 2 additions & 0 deletions tests/e2e/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { execStaleEnvScenario } from "./scenarios/exec-stale-env.ts";
import { initScenario } from "./scenarios/init.ts";
import { lifecycleHostProcessScenario } from "./scenarios/lifecycle-host-process.ts";
import { lifecycleSessionRecoveryScenario } from "./scenarios/lifecycle-session-recovery.ts";
import { mcpLogsComposeScenario } from "./scenarios/mcp-logs-compose.ts";
import { nativeComposeAdoptionJobWorktreesScenario } from "./scenarios/native-compose-adoption-job-worktrees.ts";
import { nativeComposeAdoptionRoutingWorktreesScenario } from "./scenarios/native-compose-adoption-routing-worktrees.ts";
import { nativeComposeAdoptionSourceBindWorktreesScenario } from "./scenarios/native-compose-adoption-source-bind-worktrees.ts";
Expand Down Expand Up @@ -104,6 +105,7 @@ export const ALL_SCENARIOS: readonly Scenario[] = [
execStaleEnvScenario,
portableMultiserviceScenario,
composeConfigHashDriftScenario,
mcpLogsComposeScenario,
nativeConfigComposeScenario,
nativeConfigBuildScenario,
nativeConfigDownHooksScenario,
Expand Down
Loading
Loading