| D-M4-08-0 |
2026-10-16 |
The ticket was built on an integration base and ported onto main with only its own commits; the English docs and spec of main win, and text this ticket adds is English. The rulings D-W-115 and D-W-116 it builds on are on main in docs/DECISIONS-PLANNING.md and are not copied. |
The tickets are built in parallel on integration bases; main takes each ticket as one pull request; merged names win (D-W-100, D-W-114). |
| D-M4-08-1 |
2026-10-15 |
Typed IDs are extension types over the canonical lowercase UUID text that implement Object; besides the twelve kinds of the brief there are ForumTagId, DraftId and QuietHoursId, because those entities have IDs of their own. parse takes the 36-character form in either case; its FormatException names only the length of the input. Uuid7Generator takes the clock and the randomness as functions; rand_a is a counter seeded with 11 random bits at each new millisecond, incremented within it, and on overflow the timestamp advances by one, so IDs of one generator are strictly increasing, also when the clock goes back. |
Note 1 of the brief; a seed below half the range leaves at least 2048 IDs per millisecond before an overflow (RFC 9562 6.2 method 1). |
| D-M4-08-2 |
2026-10-15 |
Every enum carries the wire code and a member unknown (code empty) that fromCode returns for any other text; AgentRuntimeState.unknown is the value of the spec itself and the fallback. test/enums_test.dart maps every enum of the entities to its schema or property in docs/api/openapi.yaml and fails when a value of the spec has no member. SystemCode also has agent_session.resume_failed of 03, which the API schema of this base does not list yet. |
Note 2 and requirement 17 of the brief (06 13 item 3). |
| D-M4-08-3 |
2026-10-15 |
The event catalogue is generated by tools/catalog/event_catalog_dart.py from the events: list of docs/spec/actions.yaml into lib/src/rules/event_catalog.g.dart, because action_catalog.py has no output hook; make event-catalog writes it and make codegen-check compares. resourceKind is the leading word of resource. decideApplyForEvent returns unknownType for a type the catalogue does not know; a type that carries resource.version but arrives without one is not applied (ignoreStale), because only a greater version may change the object. |
Notes 3 and 5 of the brief, 04 9 item 3. |
| D-M4-08-4 |
2026-10-15 |
Grouping: text and voice are one kind class, run anchors and cards each their own, system lines never group, and a negative time difference does not group. Forum order: descending by lastActivityAt (or createdAt for ForumSort.created), ties by ID descending so every device shows the same order; ForumSort.unknown sorts by activity. Cost labels: CostLabel.finalAmount stands for the brief's final, a reserved word in Dart; an unknown billing mode is labelled information only. |
Note 4 of the brief, 01 6.1 and 6.7. |
| D-M4-08-5 |
2026-10-15 |
AppConfig is the client's single source of the values of place App and of the server keys of D-W-115 and D-W-116 (auth.device_pending_ttl included; terminal.max_session_duration follows with M4-36). tools/configdoc/app_config.py reads the generated block of section 4 of the configuration reference by column position, derives the kind from the default (boolean, duration, bytes, byteRate, bitRate, pixels, integer, decimal, list), keeps the server keys as a constant with the reason, fails when one is missing or has another place, and writes lib/src/config/app_config_keys.g.dart with the table and one typed getter per key named after the key without its first segment (a collision fails the generation). make app-config writes it; make codegen-check compares whenever packages/domain is tracked, for every component selection, because the check needs only Python. |
Note 6 of the brief; reading by position works for the German and the English reference alike. |
| D-M4-08-6 |
2026-10-15 |
mergeReadState(local, incoming, reset: false): an incoming state with an older version changes nothing; a newer one keeps lastReadSeq monotonic (the higher position stays) and brings the server's counters; reset: true marks the result of markThreadUnread, which with a newer version replaces the local state. ReadState.unreadCount is optional: the snapshot (UnreadThread) delivers it, ReadState of the API carries only mention_count. UnreadCounter.resetFrom takes the thread's stored last_message_seq and unread_floor_seq and, without a delivered unread count, computes it as 03 read_states defines it (last_message_seq - GREATEST(last_read_seq, unread_floor_seq)); messages it saw above that position stay counted on top. MessageUpdated and MessageDeleted carry the previous local copy of the message, so an edit counts only a mention that was added or removed, and a deletion takes a mention back, also one the server counted. |
04 14, 03 read_states and requirement 13 of the brief (code review of M4-08); a read_state.updated on another device carries no marker of a reset (see the report). |
| D-M4-08-7 |
2026-10-15 |
The file entity is FileMeta, so it never clashes with dart:io File in the apps; copyWith of the entities cannot clear a nullable field (a new object does). The package has no runtime dependency (meta and collection are not needed); yaml is a dev dependency for the tests that read the specification. |
Note 2 of the brief and the dependency limit of its scope. |