Skip to content

M2-02 Channel behaviour and agent configuration

No Date Ruling Reason
D-M2-02-1 2026-10-19 The task names the branch and the base: feat/M2-02-channel-behavior-config from int/m1-wave46 (9c05469); the work is neither merged locally nor pushed before its integration. The base holds the core contract in its earlier language; its entries channel.behavior.read, channel.behavior.update, agent.config.read, channel.settings_changed and agent.config_changed equal those of the authoritative contract in content, so no contract change is brought in. The task overrides the brief's branch and landing steps.
D-M2-02-2 2026-10-19 putChannelBehavior runs in one unit of work without a rights change: authorize first (unreadable 404, without manage 403 forbidden (reason capability_missing) before any step-up, stale step-up 401), then the space row FOR SHARE and the channel row FOR UPDATE, then If-Match against the locked settings_version (a weak, list or non-decimal value is a mismatch, 412 with current the behaviour at that version), then the field rules, then 409 conflict for an archived channel or a channel of an archived space, then the no-op check. Parent before child as setSpaceOperator locks (03 6.3); comparing after the lock makes two parallel PUTs with one If-Match end in one 200 and one 412, and the effective operator read after the locks is the one at commit.
D-M2-02-3 2026-10-19 Field rules, all failures in one 400 validation_failed: history_backfill_limit below zero is the schema code, above channel.history_backfill_limit_max too_large; a prompt above channel.prompt_max_bytes bytes of UTF-8 too_long (not 413), stored unchanged without trimming or character check except U+0000 (invalid_characters), which PostgreSQL text cannot store; a skill reference empty required, with a C0, C1 or bidi control or invalid UTF-8 invalid_characters (kernel/textcheck), repeated duplicate, each on /skills/<i>. No detail repeats the input. The limits are read at request time: a stored value above a later lowered maximum reads back unchanged, and a PUT that keeps it fails until it is lowered. No count or length limit of skill references beyond http.max_json_body_bytes (spec gap); a reference too large for the unique index answers too_long on /skills instead of 500 (red team of M2-02). 03 leaves the codes open; the prompt is a multi-line instruction, not a display text of SEC-IN-4; the unique index (channel_id, skill_ref) makes a duplicate a field error.
D-M2-02-4 2026-10-19 Events: every effective change appends channel.settings_changed (receive action channel.read), and only a change of prompt or skills also agent.config_changed to the effective operator read after the locks (receive action agent.config.read), in this order, both with resource channel without version and the payload {channel_id, settings_version}. A channel without effective operator gets no agent.config_changed. One function, Service.settingsChanged, raises settings_version and appends them; M2-03 calls it for binding changes with the configuration flag set. 05 6 and 09 4.4 name prompt, skills and project binding; a switch-only change reaches the operator through channel.settings_changed, which it receives by the operator rule.
D-M2-02-5 2026-10-19 Audit of channel.behavior.update: one allow entry per successful request with space_id, previous_settings_version, settings_version, changed (codes require_mention, thread_require_mention, free_response, auto_thread, allow_mentioned_agents, history_backfill_limit, prompt, skills in this order), the new switches and history_backfill_limit, and skills_count; a no-op entry has no_change: true and equal versions. Never the prompt or a skill reference. SEC-AUD-5: free text never enters audit details.
D-M2-02-6 2026-10-19 Scope serves agent.config.read: for an agent the channels that are not archived (nor in an archived space) whose effective operator it is and that it reads after the acting-for intersection, and their spaces; nothing fully visible, not instance-wide. Any other kind gets 403 forbidden with subject_kind_not_allowed. listServedChannels filters with both lists, orders by channel id, drops archived channels and checks every item in filter mode (a refusal is logged without ids and counted as filter_mismatch). The skills are read in the same statement as the channels. Lists count through Scope (02 9.3), and M1-28 allowed Scope only for read-level actions; one statement keeps a list item consistent with a concurrent change.
D-M2-02-7 2026-10-19 The admission rule of sync (Admit, the catch-up pages and the gateway hub) admits a personal event whose receive action is not self only to its audience and only while authorize allows that action in filter mode; personal events with self stay as they were. 05 6 requires the receive action for every receiver; an agent that stopped operating the channel gets no agent.config_changed.
D-M2-02-8 2026-10-19 channel.settings_changed advances the structure generation of the delivery cache of authz like the structure events of 04 8.1 (spec gap reported for 04 8.1). The channel facts carry the mention switches that agent.invoke evaluates at delivery time (M2-08 on).
D-M2-02-9 2026-10-19 getServedChannelConfig and the list serve only the channel's effective operator; the fixed operator of a thread whose channel now has another operator reads nothing there (403 forbidden (reason subject_kind_not_allowed), spec gap reported). ChannelBehavior.project_binding is absent until M2-03. 05 4.10 names "agent-self and effective operator"; the binding is M2-03.
D-M2-02-10 2026-10-19 docs/api/openapi.yaml gets none of the descriptions of the brief (the rules of putChannelBehavior, the list and config wording, the codes duplicate and invalid_characters in FieldError.code); this ruling, the module README and the problem bodies carry them. The base file is 18 bytes below repo.max_file_kb (D-M6-01-9); the gate must not be loosened, and splitting the contract is an owner decision.
D-M2-02-11 2026-10-19 The port chat.AgentConfig takes the actor as a parameter like chat.Structure, not from the context. The cross-check that channel.history_backfill_limit_default stays at most channel.history_backfill_limit_max is the existing check of chat/app.Config that fails the start in wire.NewChat; configchecks.go gets no copy. channel.prompt_max_bytes is registered with minimum 1. The brief follows the code where the merged M1 code names things differently; the start already refuses the combination.
D-M2-02-12 2026-10-19 The integration tests live in test/integration/behavior, a package on harness.Main, as four worlds with subtests named after the brief's tests. Sam reads Support/Allgemein through the group Leser (G-4), so his 404 cases use Support/Eskalation; the stale step-up comes from auth.step_up_max_age of the test server. The cases with personal tokens are not built. test/integration runs on pgtest.Main with its in-process world; a harness world per brief test would start twenty servers.
D-M2-02-13 2026-10-19 Canary: the class manage_only holds the prompt and skill canaries; it is allowed only in current of a 412 of putChannelBehavior (error records name their operation) and in success bodies, which no sink captures. Alex (reads Support, no manage) becomes a full observer with gateway, listEvents and getSnapshot; Lotse's listEvents pages from cursor zero are foreign records too. Brief note 14; the operator receives agent.config_changed but never the prompt.
D-M2-02-14 2026-10-21 Port onto main from the ticket branch (9c05469..eb37ba7, integrated as one squash on int/m1-wave48) after M1-69: the four operations move from later to enabled in api/operations.yaml; the console operations of the base (M6-01) are not on main and stay later, while the agent consumer operations of M2-07 are on main and stay enabled next to this ticket's (union of the enable list, the REST layer generated again); channel.prompt_max_bytes leaves tools/configdoc/planned.yaml; the English row T-AI-09 of the threat model names SEC-T10; the route table of api/routes/real_test.go, the chat README and the changelog take only this ticket's lines; the strict objects of api/rest grew above repo.max_file_kb, so the generator takes the cut of M6-01 unchanged (splitAll, halveObjects, as D-M4-31-5 and D-M3-03-28 do); the REST layer, the route table, config-schema.json and the configuration reference are generated again; comments name the English chapter files. INT-W48/1 (the canary readable set of Alex with the file scope) belongs to the pair M2-02 and M3-03 and lands with whichever of them comes second. The PR lane ports only the ticket; English documents on main win.