| D-M4-37-0 |
2026-10-19 |
Base int/m1-wave44 at 8df8fa3, branch feat/M4-37-admin-hosts in its own worktree; the branch is not merged locally and not pushed, the PR lane lands it. The rows stand in a section of the ticket because the base has no section 10 in this file. The operations, schemas and problem codes the brief names are on the base, and the base already holds the later wording of origin/main (attempts_remaining, invalid_format), so no contract change was brought in. docs/quality/accessibility-checklist.md is not on the base: the skeleton of origin/main comes in with the section of A8 filled and a navigation entry. |
Instructions of the launcher, which override the brief's landing step; merged layout wins over the brief (D-W-100, D-W-114). |
| D-M4-37-1 |
2026-10-19 |
Names only from the reported list: the pairing rows and the assign dialog offer the names of reported_session_users that have no valid assignment and are not chosen in another row, as pickers, never as a text field. Because getHost lists valid assignments only, a name once given and ended is still offered; the server answers 409 conflict session_user_taken, and the screen explains that a name stays bound to its person on the host. 400 validation_failed with session_user_not_on_host says the host must report the name first. Ending an assignment confirms that the name stays taken. |
Brief Design notes 1 and 3; 24 2.2, 3.2, ruling T12; no own record of ended names in the client (15 1 principle 1). |
| D-M4-37-2 |
2026-10-19 |
Check results as returned: every doctor entry in the server's order with its check ID, ok or the code, and a named session user with the person of its valid assignment; for a name without one the page reads the audit of the host once (listAuditEvents, action=host.manage, resource_id of the host, details operation=session_user, user_id, os_user, the last entry per name wins) and shows "formerly assigned to"; without an entry the name stands alone with "no assignment known". The audit is read only when such a name exists, because audit.read has step-up. A failed audit read leaves the names alone with a note. Entries show for a disconnected host as well. |
Brief Design note 2; 24 2.2 rule for names, 10.1; the API has no list of ended assignments. |
| D-M4-37-3 |
2026-10-19 |
Host target in the shared wizard: AdminTargetKind.host; the target step lists the hosts of listHosts below spaces and channels (kept out of the targets that "Who has access?" explains); on a host the level step shows no level and the one fixed capability terminal, and GrantCreate carries no level. The preview shows the server's text unchanged and a "Not effective" badge for every hint terminal_not_effective; the client tells no cause apart. The host page lists the grants on the host with the server's preview of the same grant (a dry run) and the same badge. 409 i6_violation is explained everywhere (adminFailure, the LLM login section, the confirmation, the preview's invariant) with what I6 means and the server's hint. |
Brief Design note 4; 07 2.12, 9.3; 24 7.3. |
| D-M4-37-4 |
2026-10-19 |
Names that differ from the brief: the page widget is HostDetail in host_page.dart, because api_client exports a model HostPage; CreateHostDialog is the form of step 1 inside the wizard; host_labels.dart holds the labels and the reason dialog of every change ("Confirm" waits for a reason). HostsRepository (ApiHostsRepository) holds the host calls and AdminRepository includes it (as D-M4-27-4). attempts_remaining is read from the problem body by the repository (FingerprintMismatch), because ApiProblem does not carry it. The number in the fingerprint label is the reference default 8 of terminal.fingerprint_confirm_chars (the server sends no value); a prefix of another length comes back as 400 validation_failed. |
Brief Boundaries (D-W-100, D-W-114); fixed requirement 9. |
| D-M4-37-5 |
2026-10-19 |
The shell lists "Hosts" only when getMe.is_owner is true (HostsRepository.callerIsOwner); the path /admin/hosts stays reachable and the server answers. The state actions follow the life cycle of 24 3.1 from the returned state (continue pairing and re-pair in pairing and confirming; re-pair, disable, retire when active; enable only after an owner disabled; nothing when retired). Re-pairing asks a reason, issues the code and opens the wizard at step 2; after the last failed fingerprint the wizard returns to step 2, which needs a new code. The fake server stores hosts in FakeWorld.hosts, plays bothy-host pair with FakeWorld.pairHost, writes the assignment audit to FakeWorld.auditEvents, and its problems carry extra members (hint, attempts_remaining). |
Brief fixed requirements 11, 13, 17; Scope (fake handlers through the registry of M4-17). |
| D-M4-37-6 |
2026-10-19 |
Red team NEIN, fixed: the pairing wizard owns its session user rows (SessionUserRow) and sends exactly the complete rows step 3 shows whose name the host still reports; the rows survive "Back" and are dropped when the host falls back to pairing. Before, the rows lived in the row widget and the wizard sent a stale copy after "Back" or the fallback, which binds a name to a person for good on the host. The pickers of a row are keyed by the row, so removing a row keeps the others. |
Red team of M4-37 (reproduced Major); brief Design note 1; 24 2.2, ruling T12. |
| D-M4-37-7 |
2026-10-20 |
The fake server keeps one listAuditEvents: the handler of M4-29 over FakeAuditLog. The host handlers write their host.manage rows into that log (registerHostHandlers takes it) instead of FakeWorld.auditEvents, which is gone. |
M4-29 and this ticket both registered listAuditEvents; the later registration replaced the other, so either the audit filters, paging and chain or the host audit would have broken. The contract test reads the host rows through the M4-29 handler. |
| D-M4-37-8 |
2026-10-20 |
In the admin_ui test fakes the per-host audit map of FakeHosts is named hostAudit; the admin shell goldens show both entries "Audit" and "Hosts". |
FakeAdminRepository.audit (M4-29, the audit entries) and FakeHosts.audit collided; the shell now lists both sections. Test code only, no change of behaviour. |
| D-M4-37-9 |
2026-10-20 |
Port to main on top of M4-29: the ticket commits from 8df8fa3 to 5725d60 plus the two fake fixes of D-M4-37-7 and D-M4-37-8; registerHostHandlers takes the audit log of M4-29, the host page goldens are recorded again after D-M4-25-10 (the no-break space before AM and PM), and ApiHostsRepository has its own wire test (test/data/api_hosts_repository_test.dart). |
The integration branch held thread view code (M4-20) that is not on main; the port keeps only this ticket, and the wire test keeps the package above its coverage floor on new code. |