proximo

proximo remembers what the runtime declares, never what the project wrote

Amended by ADR 0008, which settles what this one left as a _Debt_ in CONTEXT.md — that notation is gone, and the two references to it below are the record of when it was still there.

ADR 0006 gave every route a Transcript and left a hole it named out loud: a container with no Route — a worker, a queue consumer, a migration job — produces no Access record, therefore no Exchange, therefore no Transcript. The two reasons the gap was declared rather than filled were that an Exchange without an Access record would hollow out the term, and that deciding which lines of a routeless container look like errors would mean interpreting the text a Transcript exists to quote. Both had to be honoured rather than overridden.

Decision. proximo may remember what the runtime declares; never what the project wrote. Four consequences, one rule:

A container with no host is observed only if it asks to be, with proximo.transcript=true — "you can be quoted", beside proximo.hosts's "this is how you are reached". Incidents themselves are orthogonal to routing: every container proximo knows about produces them, and the label only makes an otherwise invisible one known.

Considered options

Match the text. Read a routeless container's output and treat what looks like an error — panic:, Traceback, ERROR — as the thing to report. Rejected: it is the reason ADR 0006 declared the gap instead of filling it. A Transcript exists to quote output verbatim, and a matcher makes proximo an interpreter of the project's own words — wrong in both directions, since it invents errors in a logger that prints ERROR at info level and misses every framework that does not. Naming the Incident as a term is what holds this line: without a word for "a fact the runtime declared", the first panic: matcher breaks nothing that is written down.

Query container state instead of remembering. docker inspect reports an exit code, so proximo could ask at print time and store nothing at all — the purest form of the ADR 0006 rule. Rejected because it answers a different question: it reports the last exit, not the six before it, which is precisely the restart loop this exists for. Remembering tens of bytes of runtime metadata does not weaken the rule; the rule is about the project's data, and an exit code is Docker's.

Put the store in the hop, where the Exchanges already live, so the CLI reads one source. Rejected twice over: the hop deliberately has no Docker socket, being the one stack service a browser can reach, and shipping Incidents to it would add a channel between two stack services — which ADR 0006 rejected elsewhere for the same reason. The cost, a second way for proximo errors to fail, is one the model already accommodates: a listing hands over a Check's Remedy when the stack itself is why it has nothing to show.

A sibling term for the Exchange — an "Episode", say, holding an Incident and a Transcript the way an Exchange holds an Access record and one. Rejected: it doubles the vocabulary to say one thing, and every command, flag and document would then have to name both. The Transcript standing alone says the same thing with one term fewer.

An Exchange with an optional Access record. The smallest diff on paper, and rejected on the term: an Exchange is one request, and an Exchange whose Access record may be absent no longer means anything in particular. A term that admits its own absence has stopped fixing the language.

Infer the opt-in from the Compose project — observe every container of a project that already has a routed one. Rejected: it drags in postgres, redis, adminer and mailhog, and forces proximo to judge which sidecars matter, a judgement the model gives it nowhere else. It would also make a container observable on a fact the developer never wrote down, which is the opposite of how a Route works.

A separate command (proximo incidents). Rejected for the reason proximo has exactly one Skill: the developer's question is never "was it the request or the worker" — the queue is stuck because checkout enqueued a malformed job. Two commands force them to know the answer before asking. One listing, one time order, an Incident as a differently-shaped row among the Exchanges.

Consequences