# Corrections for `mermaid/TestAutomationV2.mmd`

Written 2026-08-06 against the diagram as it stood that morning. The diagram
is not edited here on purpose: it is being maintained in parallel, so this is
the change list rather than a patch.

The structure holds. The trust boundary, the gate chain, the action registry
and the output taxonomy are all still accurate. What follows is what tonight's
work invalidated.

## Nodes that are now factually wrong

**`collector`** says *"Keeps the head and tail"* and *"Keeps the earliest
useful failure regions"*. Earliest-first selection was the defect: on a long
pipeline the byte budget was consumed entirely by successful tool output, and
the enforced failure never reached Hermes. Replace with:

```
Ariadne evidence collector
Reads the full console up to 2 MB
Ranks failure regions by evidence strength
A definite failure outranks a tool that merely ran
Ignores passing test lines that quote failures
Merges repeats and overlapping context
```

**`repair_fixture`** says *"Creates one scoped Job"*. It no longer does. The
repair is an in-process ConfigMap patch; no pod is created.

**`candidate_files`** says *"Ranks the earliest hints first"*. It now also
reads the failing test and follows its imports to the module under test, which
is what made a pull request on a real service repository possible at all:

```
Candidate file selection
Uses path and line hints from failure regions
Reads the failing test as context
Follows its imports to the module under test
Reading is wider than writing: a test is readable, never patchable
```

**`action_scope`** says *"No real service classification maps to a mutation"*.
No longer true. `retry_transient_infra` is allowlisted for every allowlisted
job, so a real service can now receive an action - a Jenkins rebuild.

**`timings`** - the fixture loop is not four minutes. Measure from the red
build, not from arming, because arming depends on the agent pool:

```
Red -> fixture patched healthy: 25s
Red -> rebuild green and incident resolved: 1m04s
```

**`examples`** - supersede with the stronger 2026-08-06 results, including
`ariadne/409` opening a real pull request with the correct one-line fix.

## Structurally misleading

**The `route` decision reads as three exclusive branches.** It is not. An
escalating incident attempts a code proposal *and* files an issue *and* stays
`human_required`. A pull request rides along on the escalation; it never
replaces the issue. As drawn, a reader concludes one substitutes for the
other.

**`branch_build` overstates coverage.** `hermes-code-demo-branches` only
builds branches in the demo repository. A pull request opened against a real
service repository gets no automatic branch build. Mark it `limited`.

**`alert_output` is stale.** Alerting no longer fires on every escalation.
Two narrow rules remain: a repair that ran and failed, and an escalation
untouched for six hours. The issue is the durable artifact; email is the
exception.

## Missing, and worth adding

- The demo classification and `repair_demo_fixture` are now forbidden on any
  job other than `hermes-triage-demo`. Hermes misapplied that label to real
  services twice at 0.96 and 0.99 confidence; the gates refused it both times,
  and it can no longer be produced at all.
- **A build that never finishes** is escalated once it passes the time cap,
  with no model call, because its console is still being written. Before this
  existed such a build produced nothing anywhere while holding an agent slot.
- **A failure superseded by a newer build can be skipped entirely.** Detection
  reads `lastBuild`, so a red build replaced quickly is never triaged.

## Second pass, after the remaining work landed

Everything previously marked conditional is now done, which changes three
things structurally rather than cosmetically.

**Not every incident reaches Hermes any more.** The diagram's main flow runs
intake -> hermes_plane -> controls -> response, which reads as though every
incident is diagnosed by the model. A build that overruns its time cap is now
escalated by Ariadne alone, with no model call at all, because its console is
still being written and any root cause would be invented. That needs its own
edge from the detector straight to the human path, bypassing `hermes_plane`
and `controls` entirely. It is also the honest picture of the trust boundary:
some conclusions are Ariadne's own observations, and the issue body now says
so rather than crediting a diagnosis that never happened.

**The Jenkins evidence limitation is gone, not narrowed.** Delete
`jenkins_limit` and the "Per-test evidence waits on Jenkins plugins" line from
`current_limits`. `junit` and `pipeline-stage-view` are installed, `testReport`
and `wfapi` return 200, and the bundle now carries `failed_tests` with the
failing test name, class and assertion, plus `first_failed_stage`. The
collector always fetched both and had simply been receiving 404s. The `bundle`
node should list them as populated fields, because they are what the diagnosis
now rests on rather than scraped console text.

**The detector watches branches, not just jobs.** A multibranch project is a
folder with no `lastBuild`; it was returned as skipped on every tick. The
detector now expands a folder into its branch jobs, capped at five, so
`detector` should say it watches configured jobs *and the branches inside
multibranch folders*.

Smaller corrections to the same nodes:

- `evidence_sources` may include a service's own namespace, not only the demo
  namespace and `jenkins`, where the job is mapped to one.
- The human path files an issue even for an incident triaged before issue
  filing existed, reusing the classification the original diagnosis recorded
  so one open issue per job and classification still holds.
- Hermes's credential comes from Vault, not a manually created Secret. If the
  diagram ever shows credential provenance, that is now the accurate source.

## Conditional on work that was blocked

`model_gate` should now read `anthropic/claude-opus-5` primary, with
`openai-codex/gpt-5.6-terra` first fallback and local `gpt-oss:20b` second.

`jenkins_limit` and *"Per-test evidence waits on Jenkins plugins"* come out
once the `junit` plugin is live. Note the plugin was never blocked on a core
upgrade as previously recorded: `junit 1369.v15da_00283f06` runs on core
2.528.3, only the latest release requires 2.533.

## Third pass: the escalation branch now has an output

The chart shows the human path as a dead end for the automation: no action
fits, an issue is filed, and Ariadne learns nothing. That is no longer the
whole story.

**A diagnosis may now propose a remediation Ariadne cannot perform.** When no
allowlisted action fits, Hermes may return `suggested_remediation` naming the
action it believes would work and the evidence that should be required before
running it. So the human path forks: an escalation always files an issue, and
some escalations also carry a proposal. The chart should show that second
output leaving the escalation node, labelled as a proposal for a maintainer
rather than as anything executable.

**The proposal must be drawn outside the trust boundary.** It is prose in an
issue and a field in an audit event. No gate reads it; an id that is not
already in the allowlist still fails `action_not_allowlisted`. If the boundary
is drawn as "Hermes advises, Ariadne acts", the proposal sits firmly on the
advisory side, and the arrow from it should terminate at a person, not at the
Action registry. The registry only grows when a human deploys a new action.

**The three allowlisted actions are now four.** `action_registry` should list
`repair_demo_fixture`, `retry_transient_infra`, `reclaim_workspace_storage`
and `clear_stuck_agent_pods`, each reachable only from its own classification.

Two of those are worth calling out on the chart as separate paths rather than
folding them into a generic retry, because the distinction is the point:
`workspace_storage_exhausted` reclaims stale workspace storage *before*
rebuilding, since a plain rebuild lands on the same full volume; and
`jenkins_agent_provisioning_failure` clears finished agent pods first, since a
retry otherwise queues behind the same stuck pool.
