| D-M2-05-0 |
2026-10-20 |
Base int/m1-wave49 at eb15f40, branch feat/M2-05-runs-state-machine in its own worktree; the branch is not merged locally and not pushed as a pull request, the PR lane ports it. Brief, spec and contract were read from origin/main; the columns renewed_from_jti and invocation_held_until of 03 runs with R-DM-44 and R-DM-47, and the reasons channel_read_only, tool_class_owner_only and tool_class_mismatch of 09 5.2, which the base lacked, were brought in from main as their own commit. Text this ticket adds to the German chapters is English. The migrations take the next free numbers of the base. |
The task names the base and the lane; the merged names win (D-W-100). |
| D-M2-05-1 |
2026-10-20 |
The acceptance of 09 3.4 point 7 is built here; the personal event agent.invocation is written by M2-08 together with its delivery check. |
A written but unchecked invocation could reach an agent connection before I2 is checked. |
| D-M2-05-2 |
2026-10-20 |
With the auto thread, agent.invoke is checked on the thread of M and the new thread is created only on allow. Place: runs/app/accept.go, 09 3.4. |
No empty thread after a denial, and the mention rule of 09 3.4 point 3 and the guard judge the same thread. |
| D-M2-05-3 |
2026-10-20 |
createMessage appends the message.created events of the unit (system line, then the message) before it publishes MessagePosted (same order); the acceptance sets received through SetTriggerState (version plus one, message.updated), and the 201 and replay bodies are the stored row after the unit of work. |
A subscriber that appends a message to the same thread (the anchor) must not overtake the message in the log (04 10); 04 16.5 shows trigger_state received in the 201. |
| D-M2-05-4 |
2026-10-20 |
runs.invocation_jti of a new run is a fresh UUIDv7 that no token carries until the first delivery replaces it. |
03 requires the column and its uniqueness; no token exists at acceptance. |
| D-M2-05-5 |
2026-10-20 |
billing_mode_expected of a new run is the agent's billing mode at acceptance (agents.Facts); each delivery sets it again (03). |
The column is NOT NULL and the mode is known at acceptance. |
| D-M2-05-6 |
2026-10-20 |
The anchor is silent, written in the target thread after the run row; its client_msg_id is a server UUIDv7 and its request hash covers run-anchor:<run>, kind run and the empty content. |
09 5.4: the anchor notifies nobody; the deferred FK messages.run_id allows either order and Open writes the run first. |
| D-M2-05-7 |
2026-10-20 |
The reason table is 09 5.2 plus user_disabled (cancelled, server), which 02 4.3 names for runs cancelled by user.disable; 09 5.2 has the row. Place: runs/domain/reason.go. |
One table for the server, the adapter rule and the metrics. |
| D-M2-05-8 |
2026-10-20 |
An adapter failed keeps its reason only when 09 5.2 lists the adapter as setter; every other code, server-only codes included, is stored as adapter_error, and the original code is the detail original_reason of one server step kind = status, name = adapter_reason, status = failed, cut to run.step_detail_max_bytes after kernel/textcheck (a rejected code gives {}). 09 5.2 names this place. |
09 5.2 last sentence; a server-only code from an adapter would let it claim a server finding. |
| D-M2-05-9 |
2026-10-20 |
runs and run_steps have no channel_id (03, M1-62). A read of them either reads the rows of exactly one thread or run whose visibility was checked first (after_authorize, GetRun as authorize_resolution), or, for the active-run lists, reads the active runs, resolves their threads through chat.Threads.Locate, keeps only threads whose channel is in Scope.ChannelIDs and runs authorize per row (the new category thread_scope of the SQL visibility gate); the gauge query has no principal (system_job). 07 7.4 rule 1 says so. |
The predicate of 07 7.4 rule 1 needs a channel column; the copy would go stale when a thread moves. |
| D-M2-05-10 |
2026-10-20 |
listThreadRuns proves its route action through Scope(run.read); an invisible thread gives the same 404 as an unknown one. |
02 9.3 and 02 9.2. |
| D-M2-05-11 |
2026-10-20 |
listActiveRuns pages by (queued_at DESC, id DESC) and fills a page across batches; next_cursor is the keyset of the last examined row when the page is full and another row exists. |
Rows dropped by the filter must not shorten a page, and the next page must not repeat or skip a row. |
| D-M2-05-12 |
2026-10-20 |
Until M2-11 the follow-up outcome writes nothing; multiple_agents_mentioned writes failed without authorize and without audit. |
There is no single agent to check, and M2-11 owns follow-ups. |
| D-M2-05-13 |
2026-10-20 |
A candidate that M does not mention is first checked in filter mode; a denial with level_insufficient there writes nothing and no audit entry. |
R-AG-05: someone who may only write sees no error for every message. |
| D-M2-05-14 |
2026-10-20 |
When the decision of M2-04 rejected but authorize allows, the acceptance fails closed with the reason of M2-04 and logs trigger_decision_mismatch without IDs. |
Two rules that disagree are a bug; the safe outcome is no run. |
| D-M2-05-15 |
2026-10-20 |
Run metrics are recorded after the commit of the outermost unit through txn.AfterCommit; bothy.run.active is an observable gauge registered in the roles worker and all. bothy.agent.handle has the closed source agent_handles of the metric registry: values are checked against the handle pattern of 03 principals.handle at recording, so a new agent needs no restart. |
A retried unit would count twice; signals.md 2 item 1 counts agent handles among the sets the operator bounds. |
| D-M2-05-16 |
2026-10-20 |
The FKs of runs to agent_sessions, commands, interactions and dev_environments come with M2-12, M2-16, M2-15 and V2-01; messages.run_id gets its deferred FK here. |
The referenced tables do not exist yet. |
| D-M2-05-17 |
2026-10-20 |
SetTriggerState with the stored state and reason writes nothing and appends no event. |
A repeated delivery must not raise the version. |
| D-M2-05-18 |
2026-10-20 |
The ports gain two small read interfaces that the brief's note 6 step 2 needs and the merged ports lack: chat.TriggerFacts.TriggerChannel (effective operator and the five switches of 09 3.3, no authorize) and agents.Facts.RunFacts (handle, status, billing mode, holder and tool class of a set of agents, no authorize). |
The acceptance must read these facts through ports (02 4.4); AgentConfig authorizes manage and agents.Agents authorizes agent.read, neither fits a subscriber. |
| D-M2-05-19 |
2026-10-20 |
The acceptance is the in-transaction subscriber runs/app.Acceptor, created with the module objects of the txn registry and bound to the runs use cases in the chat step of the start; before Bind it writes nothing, so a test world built without the runs module keeps the behaviour of M1. |
The registry is built before the use cases exist (wire.NewModules), like the subscribers of sync and notify. |
| D-M2-05-20 |
2026-10-20 |
The acceptance passes AuthorHasTrigger = true and WriteOnChannel = true to Decide: the trigger right and the agent's own write level are decided by agent.invoke (filter mode first for a candidate without mention, then request mode, which audits the denial), whose result maps through Settle. A rejection that names a candidate (agent_not_allowed_here, run_active_other_agent) is checked with authorize too. |
Facts about rights come from authorize, never from a grant query (brief note 6 step 2); the audit of a denied mention needs the request-mode call. |
| D-M2-05-21 |
2026-10-20 |
03 runs allows ten transitions, not the nine the brief counts: queued has three, running four and waiting_input three targets. CanTransition and its test follow 03. |
The data model is the core contract. |
| D-M2-05-22 |
2026-10-20 |
In the integration tests the mentions of Texter come from the owner, not from Alex as the brief's test list says. |
agent.read (05, "read on a channel the agent operates") lets Alex mention only agents that operate a channel Alex reads; Texter operates Marketing, so "@texter" from Alex stays text and the rejection under test would never be reached. |
| D-M2-05-23 |
2026-10-21 |
Port onto main from the ticket branch (own commits cdb9391..20d1a89; the first commit, which ported the runs columns and reasons of main into the German chapters of the old base, is dropped because main already has them): the migrations are renumbered to 00051_runs_runs.sql, 00052_runs_run_steps.sql and 00053_messages_run_id_fk_validate.sql (the next free numbers on main; the down test goes to version 50), comments name the English chapters 03-data-model.md, 05-actions.md, 07-permissions.md and 09-agents.md, and the spec, ADR and README additions are written into the English files. Parts of the old base that are not on main stay out (pieces of other tickets in the leak fixtures, the metric registry, the canary and the test worlds); the port was replayed onto main again as M4-31, M3-03, M2-03, M6-01, M2-20, M7-02 and M2-21 landed, each time with the next free migration numbers, and the rulings and the changelog entry are fragments since the fragment change on main. Generated code, the configuration reference and the config schema are regenerated. |
main has the English chapters and its own migration numbering; the ticket must not bring code of tickets that are not merged. |