@staix/agent-hub 0.12.44 → 0.12.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +29 -0
  4. package/docs/adding-an-ai-tool.md +102 -14
  5. package/docs/agent-notes/README.md +9 -0
  6. package/docs/agent-notes/adapters.md +58 -44
  7. package/docs/agent-notes/budget.md +1 -1
  8. package/docs/agent-notes/daemon.md +9 -7
  9. package/docs/agent-notes/local-worker.md +4 -3
  10. package/docs/agent-notes/tasks.md +8 -5
  11. package/docs/agent-notes/tests.md +7 -7
  12. package/docs/operations.md +99 -7
  13. package/docs/quickstart.md +5 -1
  14. package/docs/sdlc.md +26 -8
  15. package/docs/security.md +6 -3
  16. package/docs/specs/2026-09-19-agent-hub-design.md +114 -5
  17. package/docs/verification/2026-10-21-469-r1-dashboard-tokens.md +43 -0
  18. package/docs/verification/2026-10-21-469-r2-dashboard-shell.md +54 -0
  19. package/docs/verification/2026-10-21-469-r3-dashboard-board.md +51 -0
  20. package/docs/verification/2026-10-21-469-r4-dashboard-views.md +77 -0
  21. package/docs/verification/2026-10-21-469-r6-dashboard-motion-states.md +82 -0
  22. package/docs/verification/2026-10-22-0.12.45.md +60 -0
  23. package/docs/verification/assets/469-r1-board-1440-dark.png +0 -0
  24. package/docs/verification/assets/469-r1-board-1440-light.png +0 -0
  25. package/docs/verification/assets/469-r1-board-390-dark.png +0 -0
  26. package/docs/verification/assets/469-r1-board-390-light.png +0 -0
  27. package/docs/verification/assets/469-r2-agents-1440-dark.png +0 -0
  28. package/docs/verification/assets/469-r2-agents-768-dark.png +0 -0
  29. package/docs/verification/assets/469-r2-board-1024-light.png +0 -0
  30. package/docs/verification/assets/469-r2-board-1440-dark.png +0 -0
  31. package/docs/verification/assets/469-r2-board-1440-light.png +0 -0
  32. package/docs/verification/assets/469-r2-board-390-dark.png +0 -0
  33. package/docs/verification/assets/469-r2-board-390-light.png +0 -0
  34. package/docs/verification/assets/469-r2-board-768-light.png +0 -0
  35. package/docs/verification/assets/469-r3-board-1440-dark.png +0 -0
  36. package/docs/verification/assets/469-r3-board-1440-light.png +0 -0
  37. package/docs/verification/assets/469-r3-board-390-light.png +0 -0
  38. package/docs/verification/assets/469-r3-detail-1440-dark.png +0 -0
  39. package/docs/verification/assets/469-r3-detail-1440-light.png +0 -0
  40. package/docs/verification/assets/469-r3-detail-390-dark.png +0 -0
  41. package/docs/verification/assets/469-r3-plan-1440-light.png +0 -0
  42. package/docs/verification/assets/469-r3-plan-390-light.png +0 -0
  43. package/docs/verification/assets/469-r4-agents-1440-dark.png +0 -0
  44. package/docs/verification/assets/469-r4-agents-1440-light.png +0 -0
  45. package/docs/verification/assets/469-r4-agents-390-dark.png +0 -0
  46. package/docs/verification/assets/469-r4-memory-1440-dark.png +0 -0
  47. package/docs/verification/assets/469-r4-memory-1440-light.png +0 -0
  48. package/docs/verification/assets/469-r4-memory-390-dark.png +0 -0
  49. package/docs/verification/assets/469-r4-message-1440-light.png +0 -0
  50. package/docs/verification/assets/469-r4-office-1440-dark.png +0 -0
  51. package/docs/verification/assets/469-r4-office-1440-light.png +0 -0
  52. package/docs/verification/assets/469-r4-settings-1440-dark.png +0 -0
  53. package/docs/verification/assets/469-r4-settings-1440-light.png +0 -0
  54. package/docs/verification/assets/469-r4-stream-1440-light.png +0 -0
  55. package/docs/verification/assets/469-r4-stream-390-light.png +0 -0
  56. package/docs/verification/assets/469-r6-agents-empty-390-dark.png +0 -0
  57. package/docs/verification/assets/469-r6-agents-empty-390-light.png +0 -0
  58. package/docs/verification/assets/469-r6-board-1440-light.png +0 -0
  59. package/docs/verification/assets/469-r6-board-390-dark.png +0 -0
  60. package/docs/verification/assets/469-r6-board-empty-1440-dark.png +0 -0
  61. package/docs/verification/assets/469-r6-board-empty-1440-light.png +0 -0
  62. package/docs/verification/assets/469-r6-disconnected-1440-dark.png +0 -0
  63. package/docs/verification/assets/469-r6-disconnected-1440-light.png +0 -0
  64. package/docs/verification/assets/469-r6-disconnected-390-dark.png +0 -0
  65. package/docs/verification/assets/469-r6-skeleton-1440-light.png +0 -0
  66. package/docs/verification/assets/469-r6-skeleton-390-dark.png +0 -0
  67. package/docs/verification/assets/469-r6-toasts-1440-dark.png +0 -0
  68. package/docs/verification/assets/469-r6-toasts-1440-light.png +0 -0
  69. package/docs/verification/assets/469-r6-toasts-390-light.png +0 -0
  70. package/docs/verified.json +19 -16
  71. package/package.json +5 -1
  72. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  73. package/plugins/agent-hub/server.js +9818 -9605
  74. package/src/adapters/adapter-api.ts +2 -0
  75. package/src/adapters/agy/definition.ts +3 -1
  76. package/src/adapters/claude-channel.ts +14 -5
  77. package/src/adapters/codex-appserver.ts +1 -0
  78. package/src/adapters/conformance/acp.ts +114 -0
  79. package/src/adapters/conformance/codex.ts +191 -0
  80. package/src/adapters/conformance/enrollment.ts +43 -0
  81. package/src/adapters/conformance/factories.ts +17 -0
  82. package/src/adapters/conformance/fake-paths.ts +12 -0
  83. package/src/adapters/conformance/fakes/acp-server.ts +267 -0
  84. package/src/adapters/conformance/fakes/acp-watchdog-agent.ts +10 -0
  85. package/src/adapters/conformance/fakes/app-server.ts +119 -0
  86. package/src/adapters/conformance/fakes/codex-bin.ts +15 -0
  87. package/src/adapters/conformance/fakes/jcode-agent.ts +91 -0
  88. package/src/adapters/conformance/fakes/model-server.ts +39 -0
  89. package/src/adapters/conformance/fakes/pi-rpc.ts +32 -0
  90. package/src/adapters/conformance/index.ts +11 -0
  91. package/src/adapters/conformance/kit.ts +50 -0
  92. package/src/adapters/conformance/local-pi.ts +100 -0
  93. package/src/adapters/conformance/owned-acp.ts +44 -0
  94. package/src/adapters/definitions/builtin-metadata.ts +23 -6
  95. package/src/adapters/definitions/native-cli-entry-kimi.ts +7 -1
  96. package/src/adapters/definitions/native-cli-entry.ts +7 -19
  97. package/src/adapters/definitions/native-cli-preview.ts +2 -11
  98. package/src/adapters/definitions/qwen.ts +4 -1
  99. package/src/adapters/external-metadata.ts +48 -0
  100. package/src/adapters/external-module-scan.ts +126 -0
  101. package/src/adapters/external-modules.ts +240 -0
  102. package/src/adapters/pi.ts +2 -0
  103. package/src/adapters/public.ts +7 -0
  104. package/src/adapters/registry-types.ts +10 -0
  105. package/src/adapters/registry.ts +51 -15
  106. package/src/cli/help.ts +21 -16
  107. package/src/cli/identity.ts +1 -1
  108. package/src/cli/main.ts +23 -5
  109. package/src/cli/upgrade-labels.ts +1 -1
  110. package/src/cli/upgrade-presentation.ts +1 -1
  111. package/src/cli/upgrade-restore-state.ts +3 -2
  112. package/src/cli/upgrade-runtime.ts +4 -1
  113. package/src/hub/board.ts +30 -4
  114. package/src/hub/config-apply.ts +18 -10
  115. package/src/hub/config-reload.ts +8 -5
  116. package/src/hub/config-trust.ts +1 -1
  117. package/src/hub/control-client.ts +4 -1
  118. package/src/hub/daemon.ts +95 -39
  119. package/src/hub/facts.ts +76 -13
  120. package/src/hub/hub-tools.ts +21 -5
  121. package/src/hub/instance-config.ts +17 -5
  122. package/src/hub/instance-execution.ts +20 -8
  123. package/src/hub/instances.ts +2 -1
  124. package/src/hub/peers.ts +9 -0
  125. package/src/hub/plan-conformance.ts +83 -0
  126. package/src/hub/review-findings.ts +104 -0
  127. package/src/hub/roster.ts +31 -5
  128. package/src/hub/sdlc-queue.ts +19 -4
  129. package/src/hub/sdlc-release.ts +16 -1
  130. package/src/hub/sdlc.ts +27 -12
  131. package/src/hub/settings.ts +5 -1
  132. package/src/hub/snapshots.ts +8 -6
  133. package/src/hub/tasks.ts +275 -45
  134. package/src/hub/ui.ts +2 -2
  135. package/src/ui/index.html +559 -78
  136. package/src/ui/office-model.ts +3 -1
  137. package/src/ui/peer-palette.ts +44 -0
package/docs/security.md CHANGED
@@ -112,7 +112,7 @@ and flag. The current daemon emits `agent_cli` with `delegated: true` and
112
112
  `refused: false` for the negotiated outcome. Allowed-class commands retain the peer identity and capability checks, including
113
113
  messages, proposals and reviews. Peer starts, pause and resume may delegate
114
114
  operational console authority; task assign/escalate under the flag refuse. `--as-user`, person task labels,
115
- person-authored planning/release/memory/goal writes, SDLC record, benchmark run,
115
+ person-authored planning/release/memory/goal writes, SDLC record, spec and plan-departure acceptance, benchmark run,
116
116
  `ahub recovery reattach`, a `--fresh-session` dispose that would replace a repaired
117
117
  session's conversation (or whose scope cannot be read), and the pet
118
118
  composer are refused; presentation flags such as `--color` or `--verbose` before
@@ -216,7 +216,8 @@ Claude, Codex and Kimi still follow their own file permissions and can edit a
216
216
  config file; such an edit changes participation only at the next hub start. A
217
217
  live settings write or undo applies its own roster key and does not adopt other
218
218
  file edits, and a configuration reload refuses roster changes. External ids outside the known native catalog keep their prior
219
- admission rules.
219
+ admission rules. A roster key left by an undeclared instance loads as inert and grants
220
+ nothing; the only change it accepts is its removal from `config.local.json`.
220
221
 
221
222
  Kimi's pull-only TUI owns its tool approvals and session. The person's launcher
222
223
  merges/removes only the hub MCP entry; the daemon never edits that file and
@@ -258,6 +259,8 @@ Antigravity (`agy`, #485) is optional, cloud and off roster by default. The laun
258
259
 
259
260
  Configured ACP adapters (#451 A3) are declared only by the person, in a Git-confirmed untracked `.agenthub/config.local.json`. The whole declaration (command, argv, identity, roles and mode maps) is machine-local authority: a shared or tracked declaration is ignored with a notice, settings never edit it, and changes need a hub restart. The hub runs only the declared argv, never a module or callback. Argv that selects a leader, attach, connect or remote mode is refused, and a Grok declaration needs `--no-leader`. Each start requires the pinned native identity, an exact `session/load` echo on resume, and positive ownership of the spawned process and its original process group; unknown ownership refuses, and stop signals only that verified group. An agent that hands its work to a process outside that group is outside this boundary. Without a mode map native policy is unmanaged, and there is no hub sandbox either way. Configured kinds are cloud peers (never PII), headless only and off roster by default, and the hub makes no quota, context or controlled-recovery claims for them. A read-only instance needs the native read-only mode acknowledged before a prompt and is limited to read and review hub tools, both in the published MCP list and at the host; its native non-read tool requests are rejected, and no setting or runtime change can raise it. These agents' stderr and error text are withheld from logs, and previews redact the executable, argv and session id.
260
261
 
262
+ External adapter modules (#451 A4) are the one way to run adapter code that ships outside the hub, and the pin is code authorization, not a sandbox. `adapter_modules` is read only from a Git-confirmed untracked `.agenthub/config.local.json` (never from a shared, tracked or cloned config, and never written by settings or a hub tool). Only daemon startup imports a module, from the captured bytes that matched the `sha256` pin, not by reopening the path; the file must sit inside the project with no symlinked component and not be world-writable. Its imports are bound to the running hub's own `@staix/agent-hub/adapter` and builtins (`bun:ffi` and `bun:test` excluded), and dynamic imports, `require`, `eval` and `Function` are refused by a conservative scan. Once imported, the module runs with the daemon's privileges: the 2 s import deadline stops waiting but does not cancel its code or undo side effects, and the host's default against hub-tool title auto-approval holds only while the module keeps the factory options it is given. Inspect the whole bundle before recording its hash. Refusal reasons withhold source and module error text. A loaded kind keeps its own family, cloud trust (never PII), headless start and off-roster default, and fixed-builtin capabilities are refused.
263
+
261
264
  # Transient ACP activity (#286)
262
265
 
263
266
  - Watch activity can contain file contents, thoughts and command output. It is console-role only, kept in a bounded in-memory ring and exposed only through the `watch` control request. Agent shells, conductor tools, dashboard snapshots, hub tools and memory observers cannot read it. Terminal bodies pass through the console's sanitization and wrapping helpers. Slow sockets close instead of accumulating unbounded output.
@@ -365,7 +368,7 @@ The fresh Codex reviewer (#519) needs `review_mode: "fresh"` and an installed Co
365
368
 
366
369
  The fresh Kimi reviewer (#533) needs `review_mode: "fresh"` and Kimi Code 2.2.0 with its native OAuth login; any other version, or a failed preparation, is unavailable. It is a native tool-authority boundary, not a sandbox: no Seatbelt wraps it, and same-user native code is not isolated from the filesystem or network. Each review starts a new child in a private `KIMI_CODE_HOME` with managed-provider-only config, a clean temporary working directory and an empty skills directory. Its agent may use only `hub_review` and `hub_task_show`, served by a loopback MCP session bound to the task, head and review revision that never carries the hub's control token; it has no native read, file or shell tool and sees only the screened review brief and host facts. Its `credentials/` and `oauth/` directories and `device_id` link to the person's native ones, so refresh keeps Kimi's own lock and atomic rename; no token is copied and `KIMI_DISABLE_OAUTH_LOCK` is never set. A stop revokes its verdict authority before termination; killing it mid-refresh may require a native re-login. Private tasks are never given to it, and a child that ends without a saved verdict is never approval.
367
370
 
368
- The review brief (#520) withholds private changes whole (both sides of a move) and counts them; a rule file that fails the private-data screen is named, not shown. Attributes and rules are read at the merge base, so a change cannot loosen its own review. Spec acceptance, plan review, premature-review and cost reads fetch GitHub metadata only (edit times, check states, thread authorship and resolution), never issue bodies or comments. The CI workflow keeps `contents: read` and `actions: read`.
371
+ The review brief (#520) withholds private changes whole (both sides of a move) and counts them; a rule file that fails the private-data screen is named, not shown. Attributes and rules are read at the merge base, so a change cannot loosen its own review. Spec acceptance, plan review, plan conformance, premature-review and cost reads fetch GitHub metadata only (edit times, check states, thread authorship and resolution, changed file names), never issue bodies, comments or patches. The CI workflow keeps `contents: read` and `actions: read`.
369
372
 
370
373
  ## Bounded task specs and isolated checks (#418)
371
374
 
@@ -1387,7 +1387,7 @@ upgrade sources; ordinary clients must use protocol 12 (13 from 0.12.4, with 12
1387
1387
 
1388
1388
  ## Amendment: turn-free facts at tool boundaries (issue #108)
1389
1389
 
1390
- - `src/hub/facts.ts` keeps three things apart per peer: what the hub observed in the tree (latest version and transitions per file), what it offered (bounded offers with their file versions and plans), and what the peer acknowledged (its view). Only an acknowledgement moves a view; a later boundary offers everything since the view again. Files are the paths every task of each live cohort in which the peer has an open task names (a directory expands to git's changed files against HEAD, staged or not, and its new and deleted files, 200 at most, with a note when more were cut, told until it is read back and again in a new session), contained in the project as real paths and never `.git` or anything the denylist (`src/local/deny.ts`, `local.deny`) keeps from agents, plus the last 64 it touched in its current session; a file that falls out of those is named in every offer until one naming it is read back (#112). A file the peer touched before it had a view of it is compared with its version at that touch, so a change in between is shown, not absorbed into a first look; a file it saw under a named directory stays covered after git stops listing it (put back to HEAD's bytes, or the directory moved away; 200 at most, the rest named as no longer followed until that is read back; a new session hears only the drops of its own). A directory file it has neither seen nor touched is named without a diff when it appears with bytes that differ from HEAD's (a rewrite with HEAD's bytes, listed until git refreshes its index, is no change, for the fact and for whether the peer is current; a file too large to read is compared by git's blob id of its bytes, hashed in-process, while all such files in one comparison total 64 MiB or less, and is not compared otherwise (so the answer never depends on what an earlier comparison hashed); the bytes are the raw working-tree bytes, without git's filters, so with an end-of-line conversion or LFS such a file reads as changed and is named; #112) (diffing it against HEAD would show what changed while tracking was off, a PII window, and credit unseen changes by elimination), and counts as not yet shown until that fact is read back. Only regular files of 256 KB or less are read, re-resolved at every read and opened without following a final link or blocking. A history longer than the 64 kept transitions makes the change's attribution unknown.
1390
+ - `src/hub/facts.ts` keeps three things apart per peer: what the hub observed in the tree (latest version and transitions per file), what it offered (bounded offers with their file versions and plans), and what the peer acknowledged (its view). Only an acknowledgement moves a view; a later boundary offers everything since the view again. Files are the paths every task of each live cohort in which the peer has an open task names (a directory expands to git's changed files against HEAD, staged or not, and its new and deleted files, 200 at most, with a note when more were cut, told until it is read back and again in a new session), contained in the project as real paths and never `.git` or anything the denylist (`src/local/deny.ts`, `local.deny`) keeps from agents, plus the last 64 it touched in its current session; a file that falls out of those is named in every offer until one naming it is read back (#112). A file the peer touched before it had a view of it is compared with its version at that touch, so a change in between is shown, not absorbed into a first look; a file it saw under a named directory stays covered after git stops listing it (put back to HEAD's bytes, or the directory moved away; 200 at most, the rest named as no longer followed until that is read back; a new session hears only the drops of its own). A directory file it has neither seen nor touched is named without a diff when it appears with bytes that differ from HEAD's (a rewrite with HEAD's bytes, listed until git refreshes its index, is no change, for the fact and for whether the peer is current; a file too large to read is compared by git's blob id of its bytes, hashed in-process, while all such files in one comparison total 64 MiB or less, and is not compared otherwise (so the answer never depends on what an earlier comparison hashed); the bytes are the raw working-tree bytes, without git's filters, so with an end-of-line conversion or LFS such a file reads as changed and is named; #112) (diffing it against HEAD would show what changed while tracking was off, a PII window, and credit unseen changes by elimination), and counts as not yet shown until that fact is read back. Only regular files of 256 KB or less are read, re-resolved at every read and opened without following a final link or blocking; a larger file is compared by size and mtime, except that one whose last change (ctime) is under 2 s old and 4 MiB or less is named by git's blob id of its bytes (git's racily-clean rule: Linux before 6.13 stamps files from a coarse clock, so a same-size rewrite within one tick keeps every stat field; #428), hashed once per version in a public entry point and 16 MiB in all per store per event-loop turn, the stat stamp alone beyond either; the HEAD comparison above keeps its own 64 MiB bound and is not limited by these. A version seen racy keeps its version once it settles with the same bytes, and an integration target (`tree()`) counts large files by their stat stamp only, so it does not move when one settles. Known ceilings: a racy file over the cap or past the budget misses a same-tick same-size rewrite; a racy file first seen past the budget (kept stat-only) reads as changed once when a later turn hashes it with the same bytes; a file written continuously is hashed at every look within the budget; a settled file made racy with the same bytes (chmod, `cp -p`) reads as changed once; a version first seen racy costs an open and a stat at every later look. A history longer than the 64 kept transitions makes the change's attribution unknown.
1391
1391
  - Attribution: a transition is credited only with effect evidence: a Claude Edit, MultiEdit or Write whose result equals its input applied to the file as observed at PreToolUse with no observation in between, or a Codex `fileChange` whose changed lines equal the observed diff. Everything else is shown with its attribution unknown; an agent's own verified writes advance its view and are not shown back. A Claude Read advances its view only when it returned the whole file (no offset or limit, at most 2000 lines, none over 2000 characters); a Codex read action never does (it may be partial). A diff that matches a PII pattern is replaced by a note naming the file, and a changed file whose name matches one is counted without its name.
1392
1392
  - Acknowledgement and capability: Claude's hook runs before and after every tool and at Stop (`ahub claude` injects it in a turn-free project); the pre answer's text becomes `additionalContext`, and the row Claude Code writes in its transcript for it (matched by tool use id and offer id) is the readback. Codex's offer goes in with `steerText` (bound to the running turn), and the steered input coming back as a `userMessage` item is the readback. A readback marks the peer's path verified; a new session or thread, the peer going offline, or three offers past a minute without one (checked at boundaries and every 30 seconds; facts sent with an integration request wait for the next done and never count, a refused steer is dropped, not unread, and an unanswered one stays pending, since it may have gone in) unverifies it. Until verified, a peer gets a one-line probe at most three times per session, and no offers at all once three are unread; a Claude session whose transcript the hub cannot find gets none at all and loses a verified path.
1393
1393
  - The control contract is protocol 13: `facts` carries the phase, tool, input, tool use id, session id and transcript path; `silenced` serves check-path; `send` results name held-back recipients. Protocol 12 stays an authenticated recovery source.
@@ -2007,6 +2007,18 @@ push `accepted` and show none of them.
2007
2007
  detach and pull-only attach remove that capability. Ordinary messages remain
2008
2008
  queued while busy. A notification write alone is not proof that the native
2009
2009
  model saw it; the earlier print-mode probe was inconclusive.
2010
+ - Known limit and hint behaviour (#221). Mid-turn consumption of an important
2011
+ steer was proven only on Claude Code 2.1.302, yet push-capable `WsPeer` offers
2012
+ steer to every push-capable Claude session. On an older client a dropped
2013
+ mid-turn notification stays `accepted` until a person resolves it. No client
2014
+ version gate is added: the hello does not carry the client version, and the
2015
+ oldest version on which steer works was never probed, so a gate would be a
2016
+ guess that could disable a working path. A gate needs a probe of the older
2017
+ client and an optional hello field. A tool result that carries a held-delivery
2018
+ hint keeps the queued and important counts but drops the advice to call
2019
+ `hub_inbox`, because `Bus.pull` refuses under that hold. A `hub_inbox` result
2020
+ that already states how many messages remain unread adds only the important
2021
+ count.
2010
2022
 
2011
2023
  ## Amendment: owner and reviewer read their task (issue #208)
2012
2024
 
@@ -3722,6 +3734,23 @@ After unidentifiable Codex cancellation, status carries a fixed
3722
3734
  completion cannot clear it; verified native reattachment does. No native ids or
3723
3735
  provider text reach the notice.
3724
3736
 
3737
+ ### Peer capability ports (#625 phase 6, slice 1)
3738
+
3739
+ `PeerAdapter` optionally exposes a readonly `sessionGeneration` (string or number),
3740
+ readonly `permissionModeState` (a permission mode, unknown or unmanaged), and an
3741
+ acknowledged `setPermissionMode(mode)` operation. Generic runtime permission and
3742
+ generation dispatch uses these ports without native-class enrollment. An absent
3743
+ generation port retains the existing recovery-session/owner-token fallback; a
3744
+ present but undefined generation is unknown and refuses bounded checks.
3745
+ Checkpoints fence a present generation unless `checkpointUsesSessionGeneration`
3746
+ is explicitly false. Pi keeps that exception so its owner/session-only checkpoint
3747
+ behavior is unchanged. BasePeer installs no default generation property.
3748
+ Read-only and unmanaged ceilings, never-ask confirmation, serialization and the
3749
+ current-owner/not-offline check after acknowledgement remain authoritative.
3750
+ Codex proxy/startup/restoration-debt and Claude launch-generation/unattended-hook
3751
+ proofs remain native branches; typed semantic ports for those proofs belong to a
3752
+ later slice.
3753
+
3725
3754
  ## Amendment: playbook lifecycle and criteria checklist, 2026-10-18 (#481 S0/S8)
3726
3755
 
3727
3756
  - The AI-Native SDLC Playbook supplies the built-in stage model of an enabled lifecycle: intent (Plan), spec (Design), plan and build (Build), test (Test), review, merge and release (Deploy), maintain (Maintain). `mode` advise or enforce without `stages` selects this list. Off (no file, or `mode = "off"`) keeps the seven #257 stages, so its config, receipts, `hub_sdlc_record` schema and status output stay byte for byte. An explicit #257 list still loads: off keeps it as written, and an enabled lifecycle keeps its order under canonical ids (spec, plan, build, review, test, merge, release). Any other list needs definitions.
@@ -3733,6 +3762,10 @@ provider text reach the notice.
3733
3762
  - `ahub sdlc export`: a plain off lifecycle (no retired setting) keeps the #257 export bytes (schema 1, `workflow: "none"`, `phase: null`). An enabled or retired-setting lifecycle emits schema 2: sanitized state (id, state, stage, criteria view or a private stub), `stateHash` and `sourceHash`, without the workflow field. In off, a `phase` receipt write also keeps its #257 bytes (`tasks: []`); task import stays retired in every mode, so a non-empty list is refused.
3734
3763
  - Contract amendment, seeds (419 before and after: 414 byte-unchanged, 3 modified, 2 removed, 2 added; the registry baseline is unchanged). Modified, each with the same id, test file, test name and mutation intent: `workflow-reviewer-only` (the criteria/phase check), plus `override-local-schema` and `lifecycle-native-schema`. The person approved the last two on 2026-10-18, because the `lifecycleCriteria` plumbing changed their call sites. Removed with the code they mutated: `workflow-exact-after` and `lifecycle-phase-view-required`. Added for the new guards: `criteria-task-import-retired` and `sdlc-workflow-window-closes`. Tests that pinned the retired gsd-lite behaviour (task import, phase naming, the workflow projection, the implement id stored in an enabled config) are amended in the same PR; every other test is unchanged.
3735
3764
  - S0/S8 implement none of S1-S7 and S9-S11. The issue stays the spec, Tasks owns mutations and GitHub owns PR, check and tag facts.
3765
+ ### S3 structured findings clarification (#481, hub task #196)
3766
+
3767
+ Open Important findings carry across reviewed heads until the reviewer explicitly marks fixed or false_positive, or the person accepts with a rationale; nits do not carry. Review requests list carried public ids, pass, severity and optional path, with private details limited to counts. Use a shared valid-head resolver (PR, reviewed, local, then valid commit metadata); enforce refuses a non-empty report with no resolvable head. Recurrence uses semantic pass/id pairs excluding false positives and a durable card marker rather than its title.
3768
+
3736
3769
  ## Reviewer separation amendment (#450, hub task #223)
3737
3770
 
3738
3771
  Owner decision 2026-10-18 (#450 comment 6248184754), R1-R5. R3's mechanism, chosen by the person: a new headless Claude session per review request.
@@ -3815,7 +3848,13 @@ never initializes that app-server on the TUI's behalf. `ahub <kind> --as <id>`
3815
3848
  selects the declared identity; selection is removed from native passthrough.
3816
3849
  Missing `peer_instances` evidence on an old daemon/upgrade target is a refusal
3817
3850
  before instance runtime effects, rather than a guess that the instance is a base
3818
- peer.
3851
+ peer, but only while a declared instance is live or unreadable: a source whose
3852
+ status shows every declared id offline proceeds, because there is no extra
3853
+ identity left to reinterpret. The refusal asks the person to end extra native
3854
+ sessions and, only when an extra session cannot be ended, to remove the
3855
+ declarations temporarily. A roster key left behind by an undeclared instance
3856
+ loads as inert and `ahub roster remove <id>` deletes it; any other unknown key
3857
+ is still an unsupported peer.
3819
3858
 
3820
3859
  Each additional implementer requires an explicit registered Git worktree at
3821
3860
  `<project>/.worktrees/<id>` with an attached branch. I2 creates no worktree or
@@ -3953,9 +3992,19 @@ S9 privacy and notice ceilings: private build work with an issue ref remains ina
3953
3992
 
3954
3993
  - Task plans carry explicit review scope (none/delta/full, bounded reviewer count) and CI scope (focused/full/reuse); S9 receipts copy the planner-selected values. None uses zero reviewers; other scopes use one to eight. Legacy scope is unspecified, readable and never guessed. Advise reports the gap; new enforce receipts require both scopes, while old scope-less receipts remain valid to their next non-release planning revision under the complete-history S11 exception. Omitted cost keys on an owner file-list update retain the reviewed scope; explicit prospective drift refuses enforce acceptance. Scope does not change reviewer authority or bypass CI; S2 owns actual conformance comparison.
3955
3994
  - Review admission is separate from #520 brief/coverage. Before done/cohort writes, enforce requires the task's exact PR head, successful configured checks (native default: both OS checks and seeded guards), and no unanswered pending bot thread. Resolution settles a thread; an unresolved thread needs a user reply after the last bot comment. Only metadata is fetched. Advise reports gaps, including a generic unknown-readiness notice when a private turn finishes its own PII task without remote metadata; ordinary work remains refused during a private turn. Off adds no readback. Identity/privacy/owner and runtime authority are rechecked across awaits and each external read. Background role maintenance may retain a saved role while holding an unadmitted review envelope; a manual reviewer resend performs fresh admission.
3956
- - Whole-workflow repository serialization closes the admission race, keeping required check names and read-only permissions; queue:max retains up to 100 pending controllers instead of canceling the previous pending one. Identical-tree successful full proof is reused for PR and push. Reuse expands both OS matrix names and reports the required seeded aggregate, using only a `Reuse verified identical-tree full gate` marker step with the original run id. These successful reporting jobs and skipped jobs are not fresh full proof; duplicate active full gates and unknown active coverage refuse admission. Pending controllers do not run gates. CI proof searches are bounded; missed older success can require a new gate, never invent green evidence.
3995
+ - CI controllers are serialized per pull request (`check-pr-<number>`, `queue: single`: a newer pending run replaces the older pending one) and for main pushes and ordinary dispatches (`check-main`, `queue: max`; a macOS timing dispatch has its own `macos-timing` group), for the whole workflow and without cancelling an admitted controller (#609). A superseded PR head that is already running completes; this is a cost ceiling, not a proof gap. Two groups admitting one tree at the same instant can run it twice; the duplicate costs one run and never yields a false green. A visible active same-tree gate in any group still refuses admission. Required check names and read-only permissions are unchanged. Identical-tree successful full proof is reused for PR and push. Reuse expands both OS matrix names and reports the required seeded aggregate, using only a `Reuse verified identical-tree full gate` marker step with the original run id. These successful reporting jobs and skipped jobs are not fresh full proof; duplicate active full gates and unknown active coverage refuse admission. Pending controllers do not run gates. CI proof searches are bounded; missed older success can require a new gate, never invent green evidence.
3957
3996
  - Cost reporting is read-only over bounded current public board links plus merged-PR/check metadata: review handoff rounds and elapsed time, CI run ids and extra attempts, full re-reviews versus deltas, and unknown scope. Private/relinked counterparts are excluded at each read and projection. Pure band-breach proposal data is delivered here; the S7 baseline/configuration/automatic board-card scheduler is deliberately not implemented in S10.
3958
- - Nonblocking policy ceilings stay unchanged and are recorded in hub task #230: fail-closed metadata errors, repository-wide queue fairness, unknown thread author kinds, and strict advise readback races. Their planner-reviewed alternatives are deferred.
3997
+ - The nonblocking policy ceilings deferred to hub task #230 are settled in the follow-up below.
3998
+
3999
+ ### S10 policy ceilings follow-up (#481, hub task #230)
4000
+
4001
+ These rules are settled here and land with hub tasks #284-#286; until then the S10 behaviour above holds.
4002
+
4003
+ - CI admission separates lost proof from active-coverage uncertainty. If a commit or jobs read for one prior successful run is missing (404), times out on its own per-read limit or is malformed, that run is dropped as reuse proof; if the success list itself fails that way, all reuse proof is dropped. Admission then continues and can require a fresh full gate, never reuse. Any other read failure (5xx, 403, 429, the response size bound, authentication) stays unknown, and an exhausted read budget or overall deadline is unknown even when it surfaces as a per-read timeout. Unknown active coverage still refuses admission, as before: an unreadable, truncated or unidentifiable in-progress inventory, or an exhausted read budget or deadline. The release reader (`--require`) still refuses without a verified identical-tree proof. Degrading unknown active coverage to a fresh run is not adopted; it would need a person ruling against #609.
4004
+ - Review-thread authors are classified only by `__typename`. A `User` comment answers. Every other author counts as a pending bot comment: `Bot`, a null or deleted author, `Organization`, `Mannequin`, `EnterpriseUserAccount` or any unlisted kind. An unresolved thread is therefore settled only by resolution or by a `User` comment after its last non-`User` comment. Invalid timestamps and truncated pages remain unavailable evidence. No comment body, login or other author field is read.
4005
+ - In advise, a done or delayed-completion review-admission readback during which only quiet events (`QUIET_EVENTS`: answer, reviewer changed, idle sweep, memory reference started) were appended to the task is discarded, and the transition continues with a generic unknown-readiness notice and no further external read. A quiet `reviewer changed` entry, or a changed reviewer field, does not refuse in advise: the transition re-derives the reviewer from the current task before it sends the review. Any other drift still refuses in advise: another history event, a change of mode, owner, privacy or receipt, a revoked conductor authority, or a private turn on ordinary work. Each transition's final owner/state/check fences run before its writes. Enforce refuses any drift, unchanged. A manual reviewer assignment or retry keeps its own readback rules.
4006
+ - A delayed check whose completion is refused tells the owner, privacy-screened, to call `hub_task_done` again, but only when the task is still `in_progress` and its owner is the one that started the check (the same condition as the late-result branch of `finishChecked`); otherwise the console notice alone is recorded.
4007
+ - Fixtures pin each rule before its implementation lands (`test/ci-reuse.test.ts` for admission: a lost row then proof reuses, lost rows without proof run, active-scan failures and budget exhaustion stay unknown, the release refusal stays; `test/cost-governance.test.ts` for thread authors and advise readbacks). The dead `findVerifiedRun` reader is removed with the admission change.
3959
4008
 
3960
4009
  ### S11 release-train amendment (#481, hub task #216)
3961
4010
 
@@ -4077,5 +4126,65 @@ Configured ACP permission requests never use hub-tool titles for automatic appro
4077
4126
  - Transport selection: `acp` (stdio JSON-RPC), `codex-appserver`, `claude-channel`, `ws-pull` (pull-only workspace MCP plugin), and `hub-native`.
4078
4127
  - Conformance kit (`test/conformance/`): enrollment invariants, delivery occupancy, watchdog expiry, process-group teardown, and explicit reasons in `UNSUPPORTED` (per kind) and `UNSUPPORTED_LEGS` (per ACP leg).
4079
4128
  - Review and verification checklist: T0 receipt, path protection (`AGENT_CONFIG_SEGMENTS`), PII safety (`trust.onPrem`), process isolation, and documentation freshness.
4080
- - Config-only generic ACP adapters (#451 A3, board task #217) follow the A3 amendment above, including its isolated native-proof boundary. External adapter modules (#451 A4, board task #135) remain planned; their hash-pinning and module trust rules land with A4.
4129
+ - Config-only generic ACP adapters (#451 A3, board task #217) follow the A3 amendment above, including its isolated native-proof boundary. External adapter modules (#451 A4, board task #135) follow the A4 trust boundary below.
4081
4130
  - `docs/agent-notes/adapters.md` keeps only the per-adapter rules that bind code, pointing to `docs/adding-an-ai-tool.md` and `src/adapters/registry-types.ts` for conceptual architecture and definitions.
4131
+
4132
+
4133
+ ### A4 external module trust boundary (2026-10-21, #451)
4134
+
4135
+ `adapter_modules: [{ path, sha256 }]` is accepted only from an untracked,
4136
+ machine-local `.agenthub/config.local.json`. Shared, tracked, cloned or unknown
4137
+ provenance never evaluates module code. The declaration is person-owned,
4138
+ never settings-editable, and restart-bound. The daemon imports modules at
4139
+ startup; a CLI read, preview, reload or watcher never imports them.
4140
+
4141
+ Each pinned entry is a self-contained JavaScript bundle inside the canonical
4142
+ project root. Refuse symlink components, a file outside that root, a
4143
+ world-writable or nonregular entry, an unreadable/oversized entry and a SHA-256
4144
+ mismatch. A module bundles its own dependencies; core adds no third-party
4145
+ runtime dependency. Runtime permits static imports of Node/Bun builtins except
4146
+ `bun:ffi` and `bun:test`, plus the public `@staix/agent-hub/adapter` entry only.
4147
+ The `/conformance` export is for the module author's CI, not runtime modules.
4148
+ Relative imports, other package imports, dynamic/nonliteral imports, require,
4149
+ eval and Function-based code loading that the loader can detect are refused.
4150
+ Dependencies must be bundled as ESM; `import.meta.require` and CommonJS loading
4151
+ are refused. Use Bun.Transpiler's import records and token inspection before
4152
+ evaluation. After checking the original content pin, compile its scanned static
4153
+ SDK imports to file URLs of the running daemon's own adapter entry, never a
4154
+ project-resolved package copy. Re-hash the original captured bytes immediately
4155
+ before importing the rewritten result; never reopen the mutable source path.
4156
+ A pin is code authorization, not
4157
+ a sandbox: approved module code runs with daemon privileges; scanner rejection
4158
+ is deliberately conservative and is not a proof against obfuscated code.
4159
+ Refuse a failed module independently with a bounded, body-free status reason;
4160
+ keep builtins and other verified modules available. When trusted module
4161
+ declarations exist, unknown roster ids remain inert, including on CLI config
4162
+ reads while no daemon metadata is available; a roster row grants no adapter identity.
4163
+
4164
+ A module exports `definition` with the current `ADAPTER_API`. Validate the
4165
+ accepted structural subset and fixed-builtin capability exclusions before
4166
+ publishing it in the immutable project catalog. The initial accepted executable
4167
+ subset is a headless ACP definition (`transport: "acp"`, `start.daemonPort:
4168
+ "acp"`), with a factory returning the public AcpPeer transport for its bound id.
4169
+ Its own family, optional/off-roster defaults and cloud/unsandboxed trust are
4170
+ mandatory. Native hooks, budgets, configuration callbacks, reviewer backends,
4171
+ instance policies and other fixed-native fields are refused rather than
4172
+ advertised as usable extension points. A broader transport needs a reviewed
4173
+ kind-neutral host-port contract first (#625 phase 6). Builtin collisions, duplicate
4174
+ kinds, incompatible API and unsupported fields refuse the module. Never add
4175
+ a module to the process-global builtin registry. Builtin and configured instance
4176
+ namespaces (`<kind>-*`) are reserved against external module kinds.
4177
+
4178
+ The CLI receives external kinds only as bounded id/display metadata from the
4179
+ running daemon. That projection cannot grant permissions or register code.
4180
+ A4 refuses declared instances of external kinds with an explicit unsupported
4181
+ reason; their execution-root, recovery and CLI declaration contracts are a
4182
+ follow-up. The exported Bun conformance entry exposes callable transport
4183
+ registrations and packaged fake transports without executing those fakes on
4184
+ import. Builtins keep their existing applicable contract tests and explicit
4185
+ unsupported-leg reasons; an external module exercises the same contracts in
4186
+ its own CI.
4187
+
4188
+ ### S2 plan conformance boundary (#481)
4189
+
4190
+ Optional proof/risks lists are bounded to 32 strings of 512 characters. Literal file/directory-prefix coverage uses complete current PR file metadata, never patches or glob execution. Root plan entries never cover all files; renamed/copied files whose old identity is unavailable make the inventory unknown. Untouched paths are advisory; uncovered changed paths block enforce approval until plan amendment or person acceptance. Host content revisions are separate from backlog planningRevision. "Plan revisions after the first observed push" binds the first observed remote head to plan fingerprint/revision; earlier pushes are unknown. Pending observations persist only at successful owner/reviewer transitions; status and queue reads never append history, and off mode captures no S2 observations or content revisions. Lost pending intervals remain unknown. The ceiling is no true push-event source; the upgrade path is a webhook or Actions push receipt. Person-only `ahub sdlc accept-departure <task> <head> <reason...>` records complete current repo/PR/head inventory and plan fingerprint/revision, with bounded sanitized privacy-screened rationale; changed identity, plan or inventory invalidates it, including plan edits while reporting is off. Departure consent expires on host restart to prevent unpersisted invalidations from reviving; the upgrade path is a durable host observation journal. Private projections reveal only existence, and unavailable private inventories stay unknown.
@@ -0,0 +1,43 @@
1
+ # #469 R1: dashboard design tokens, screenshots and layout check
2
+
3
+ Task #170, branch `feat/469-r1-tokens`. R1 changes the style block, the peer palette variants and the contrast test. It changes no
4
+ layout, view, key or behaviour.
5
+
6
+ ## Source of the screenshots
7
+
8
+ A scratch hub created under a `mktemp`-style directory outside any git repository (its own `AGENTHUB_HOME`, never the person's
9
+ `~/.agenthub` or live hub), with five fake pull-only peers (claude, codex, kimi, pi, local), eight sample tasks (one in progress,
10
+ one backlog) and one console message. Rendered by a headless Chromium (Chrome for Testing 156) over the DevTools protocol with
11
+ `prefers-color-scheme` emulated, at 1440x1000 and 390x900. The Board view is shown: it is the default view, and the one R1's rules
12
+ touch most. The other views use the same tokens.
13
+
14
+ | | light | dark |
15
+ | --- | --- | --- |
16
+ | 1440 px | `assets/469-r1-board-1440-light.png` | `assets/469-r1-board-1440-dark.png` |
17
+ | 390 px | `assets/469-r1-board-390-light.png` | `assets/469-r1-board-390-dark.png` |
18
+
19
+ Computed `body` background in the run: light `rgb(238, 238, 241)`, dark `rgb(18, 19, 22)`; `scrollWidth` equals the viewport width at
20
+ both sizes in both themes (no sideways scroll).
21
+
22
+ ## "No layout change", measured
23
+
24
+ The same sample hub was rendered from `origin/main` (c4b1c998) and from this branch, and the rectangles of the header, the nav, its
25
+ buttons, the board section, the progress strip, the agent slots, the columns and the cards were compared (148 rectangles over the
26
+ four size and theme combinations).
27
+
28
+ - Exact pixel equality does not hold, and is not claimed. R1 snaps spacing to the 4px scale, so three paddings grew from 10px to
29
+ 12px: the board column (`.board-column`, 10px to 12px: columns 600 px to 635 px tall at 1440), the agent slot
30
+ (`.agent-slot`, 10px to 12px: 166 px to 178 px) and the 14px/18px section and row paddings moved to 12px/16px (`.section-head` 14px 18px to 12px 16px). Cards keep their height
31
+ except one empty-state card (47 px to 68 px) and are 3 px narrower at 1440 because their column is 4 px narrower inside. Nav
32
+ buttons are up to 4 px wider (control padding 6px/10px to 8px/12px; their 44 px minimum height hides the vertical change). The whole 390 px page is 78 px taller (2905 to
33
+ 2983) and the 1440 px page 16 px taller (1595 to 1611).
34
+ - Structure is identical in all 16 checks (4 selectors x 4 combinations): the same number of columns and rows, the same reading
35
+ order, 5 board columns at 1440 and 1 at 390, 8 nav buttons in 1 row at 1440 and in 2 rows at 390, the same 3 card columns.
36
+ - Nothing scrolls sideways at 390 or 1440 px after the change.
37
+
38
+ ## Not covered
39
+
40
+ - Only the Board view was photographed. Office, Stream, Agents, Message, Memory and Settings were not, so their light and dark
41
+ rendering is covered by the token and contrast tests and by reading the CSS, not by a picture.
42
+ - 768 and 1024 px were not photographed (they belong to R2's AC3).
43
+ - Chrome only; no Firefox or Safari rendering was run.
@@ -0,0 +1,54 @@
1
+ # #469 R2: dashboard app shell, screenshots and width check
2
+
3
+ Task #171, branch `feat/469-r2-shell` (on top of `feat/469-r1-tokens`). R2 adds the shell (top bar, sidebar or tabs), the icon sprite
4
+ and the view keys. It keeps the view names, their order, the `agent-hub-view` cookie, the pinned ids and every action.
5
+
6
+ ## Source
7
+
8
+ A scratch hub outside any git repository, with its own `HOME` and `AGENTHUB_HOME` (never the person's home or live hub): five fake
9
+ pull-only peers, sixteen to twenty-four sample tasks, one console message. Headless Chromium (Chrome for Testing 156) over the
10
+ DevTools protocol with `prefers-color-scheme` emulated. Every one of the seven views was visited at 1440, 1024, 768 and 390 px in
11
+ light and dark (56 combinations), and one representative set is kept here.
12
+
13
+ | View and size | image |
14
+ | --- | --- |
15
+ | Board 1440, light | `assets/469-r2-board-1440-light.png` |
16
+ | Board 1440, dark | `assets/469-r2-board-1440-dark.png` |
17
+ | Board 1024, light | `assets/469-r2-board-1024-light.png` |
18
+ | Board 768, light (tabs) | `assets/469-r2-board-768-light.png` |
19
+ | Board 390, light | `assets/469-r2-board-390-light.png` |
20
+ | Board 390, dark | `assets/469-r2-board-390-dark.png` |
21
+ | Agents 1440, dark | `assets/469-r2-agents-1440-dark.png` |
22
+ | Agents 768, dark | `assets/469-r2-agents-768-dark.png` |
23
+
24
+ ## AC3: nothing scrolls the page sideways
25
+
26
+ Measured in the page, not assumed: for each of the 56 combinations the document's `scrollWidth` was compared with the viewport width
27
+ and every element past the right edge (outside the nav) was listed.
28
+
29
+ - 1440 px: `scrollWidth` 1440 in all 14 view and theme combinations. 1024 px: 1024. 768 px: 768. 390 px: 390.
30
+ - No element extended past the viewport in any of the 56 combinations.
31
+ - The nav is a column of width 199 px at 1440 and 1024, and a row at 768 and 390 whose own `scrollWidth` (769) exceeds its
32
+ `clientWidth` (768 or 390): the tabs scroll inside the nav, as designed, and the page does not.
33
+ - The top bar is 61 px tall from 1024 px up and 129 px at 768 and 390 (it wraps below 1024 px; the screenshots show it).
34
+
35
+ ## AC4: keys
36
+
37
+ Tested through the page's own handler in the navigation test harness (`test/dashboard-views.test.ts`): `1`-`7` switch the seven
38
+ views in order and write the cookie, `?` opens the key list, the buttons open and close it, and view keys are ignored inside an
39
+ input, a textarea, a select, an editable node, with Ctrl, Alt or Cmd held, and while the key list or the task detail is open.
40
+ `Esc` is the native dialog's own close and is not handled by the page. Six deliberate breakages of the key handler each fail a test (a seventh, accepting multi-character keys such as `01`, first survived and led to a stronger test), and eight breakages of the sprite, labels and shell CSS each fail a test too. Not covered: a
41
+ real browser key press (no browser-driven test of the keys was run), and `Esc` itself, which rests on the dialog element.
42
+
43
+ ## Icons
44
+
45
+ Nine Tabler Icons (outline) in one inline sprite, path data copied unchanged from tabler-icons; the MIT notice is in
46
+ `THIRD_PARTY_NOTICES.md`. A test checks the count (at most 20), one stroke width, path-only symbols, no external reference, no
47
+ unused or undefined symbol, `aria-hidden` on every icon, and a text label or `aria-label` next to each.
48
+
49
+ ## Not covered
50
+
51
+ - Screenshots show Board and Agents only, at the sizes above; Office, Stream, Message, Memory and Settings were measured for width
52
+ but not photographed.
53
+ - Chrome only. The sidebar's sticky height uses `100vh`, which on a mobile browser with a collapsing toolbar was not tried.
54
+ - The "tabs scroll sideways" behaviour at 390 px is shown by the cut-off fourth tab in the screenshot, not by a scroll gesture.
@@ -0,0 +1,51 @@
1
+ # #469 R3: dashboard board, side sheets and the Plan sheet
2
+
3
+ Task #172, branch `feat/469-r3-board` (on top of `feat/469-r2-shell`). R3 changes the cards, the task detail and where the five board
4
+ forms live. It keeps drag and drop, the Pull and Assign buttons, the Ready and Waiting subheads, "Show N older" and every action.
5
+
6
+ ## Source
7
+
8
+ A scratch hub outside any git repository with its own `HOME` and `AGENTHUB_HOME` (never the person's home or live hub): five fake
9
+ pull-only peers, eight sample tasks (one in progress, backlog tasks, no owner on some). Headless Chromium (Chrome for Testing 156)
10
+ over the DevTools protocol with `prefers-color-scheme` emulated, at 1440x1000 and 390x900.
11
+
12
+ | What | image |
13
+ | --- | --- |
14
+ | Board 1440, light | `assets/469-r3-board-1440-light.png` |
15
+ | Board 1440, dark | `assets/469-r3-board-1440-dark.png` |
16
+ | Board 390, light | `assets/469-r3-board-390-light.png` |
17
+ | Detail sheet 1440, light | `assets/469-r3-detail-1440-light.png` |
18
+ | Detail sheet 1440, dark | `assets/469-r3-detail-1440-dark.png` |
19
+ | Detail sheet 390, dark | `assets/469-r3-detail-390-dark.png` |
20
+ | Plan sheet 1440, light | `assets/469-r3-plan-1440-light.png` |
21
+ | Plan sheet 390, light | `assets/469-r3-plan-390-light.png` |
22
+
23
+ ## AC5: cards and the detail sheet
24
+
25
+ Measured in the page for eight cards at each of the four size and theme combinations:
26
+
27
+ - every card has two children (the head with the avatar and the title, and one line with the badge and the release chip), exactly one
28
+ badge, exactly one owner avatar, and a title no taller than two lines (`title height / line height` was 2 at most);
29
+ - the page does not scroll sideways (`scrollWidth` equals the viewport at 1440 and 390);
30
+ - the detail sheet is open and pinned to the right edge at 1440 (x 920, width 520, the full 1000 px height) and fills the screen at 390
31
+ (x 0, width 390, the full 900 px height); the Plan sheet likewise (x 880, width 560 at 1440).
32
+
33
+ The detail groups its lines into Status, People, Dates and Links. Each original line (class, stage, waiting, rework, check, SDLC,
34
+ overdue, owner, reviewer, guidance, description, planned and actual dates, release, GitHub link, pinned) appears exactly once, in its
35
+ group; a group with nothing to show is left out. Group headings are `h4`, not `h3`, because the board tests find the five columns by `h3`.
36
+
37
+ ## AC6: the Plan sheet
38
+
39
+ The five forms (`planning`, `split-backlog`, `release-new`, `release-plan`, `propose`) moved out of five `<details>` blocks (plan, split, releases, delivery evidence, propose) into one native
40
+ `<dialog id="plan-dialog">` opened by a Plan button in the board toolbar. Their controls were compared with `origin/main`: identical
41
+ ids and order in all five (11, 4, 3, 6 and 5 controls), and every submit handler still attaches by id. The Releases panel, the delivery
42
+ evidence panel and the split panel keep their ids. A test now pins the full control list of each form.
43
+
44
+ ## Not covered
45
+
46
+ - Drag and drop, hover and the pressed (dragging) shadow were not exercised in a browser: the drop handlers are unchanged and covered by
47
+ the existing board tests, and the hover and drag styles are CSS only, read, not seen.
48
+ - Only the sample data above was rendered: no release chip, no GitHub link, no SDLC lifecycle and no rework round appear in the
49
+ screenshots; those lines are covered by the DOM tests only.
50
+ - The sheet slide-in animation was not observed, and `Esc` rests on the native dialog.
51
+ - Chrome only.
@@ -0,0 +1,77 @@
1
+ # #469 R4 and R5: the other dashboard views, glyphs to icons, Office colour checks
2
+
3
+ Task #173, branch `feat/469-r4-views` (on top of `feat/469-r3-board`). R4 restyles Agents, Stream, Memory, Settings and Message and
4
+ replaces the symbol glyphs. R5 puts the Office scene's colour use under the contrast test. Behaviour, view names and order, the view
5
+ cookie, element ids and every action are unchanged.
6
+
7
+ ## Source
8
+
9
+ A scratch hub outside any git repository with its own `HOME` and `AGENTHUB_HOME` (never the person's home or live hub): five fake
10
+ pull-only peers, seven sample tasks plus a backlog task, four short-term memory notes (decision, fail, contract, finding) and three
11
+ console messages. Headless Chromium over the DevTools protocol with `prefers-color-scheme` emulated, at 1440x1000 and 390x900.
12
+
13
+ | What | image |
14
+ | --- | --- |
15
+ | Agents 1440 light / dark | `assets/469-r4-agents-1440-light.png`, `assets/469-r4-agents-1440-dark.png` |
16
+ | Agents 390 dark | `assets/469-r4-agents-390-dark.png` |
17
+ | Stream 1440 light, 390 light | `assets/469-r4-stream-1440-light.png`, `assets/469-r4-stream-390-light.png` |
18
+ | Memory 1440 light / dark, 390 dark | `assets/469-r4-memory-1440-light.png`, `assets/469-r4-memory-1440-dark.png`, `assets/469-r4-memory-390-dark.png` |
19
+ | Settings 1440 light / dark | `assets/469-r4-settings-1440-light.png`, `assets/469-r4-settings-1440-dark.png` |
20
+ | Message 1440 light | `assets/469-r4-message-1440-light.png` |
21
+ | Office (Flat) 1440 light / dark | `assets/469-r4-office-1440-light.png`, `assets/469-r4-office-1440-dark.png` |
22
+
23
+ The rig also shot the other combinations (all six views at 1440 and 390, light and dark); only the ones above are attached.
24
+
25
+ ## R4
26
+
27
+ - **Agents.** Every peer row (five of five) has one summary line (queued count, "Working on #n" or "No active task") and exactly one
28
+ closed `<details class="peer-fold">` holding the former meta lines. Row height was 144 to 145 px with the fold closed. The status
29
+ notice stays outside the fold. Approvals stay first.
30
+ - **Stream.** The filter chips and Follow latest are in the section head, above the scrolling list. Each event is a head line (time,
31
+ sender, kind) above a monospace body; in the page the left edge of every event was the same x (249 at 1440, 29 at 390). That is
32
+ aligned rows, not four separate columns: the spec's "time column, kind, sender, body" is met as one head line plus the body.
33
+ - **Memory.** Rows show title, a kind badge and a meta line with the id, peer and date and time (not a relative age); the selected row has an accent edge. The graph
34
+ and the detail pane use tokens only. With four notes: four rows, one pressed, the detail pane filled.
35
+ - **Settings.** Six groups as sections. The reload row now has padding, and a "restart pending" badge and a count by "Pending
36
+ restart" exist in the markup. The text lines, Preview, Save and Undo are as before.
37
+ - **Message.** Same form, capped at 640 px, with the shared field and label styles.
38
+ - **No page scrolls sideways** in any of the six views at 1440 or 390 in either theme (`scrollWidth` equals the viewport).
39
+
40
+ ## AC7: glyphs
41
+
42
+ - A test fails if any of `○ ● × ‖ ⚑ ★ → ⟳` is back as a quoted string in the page, and one fails if an Office shape name is renamed.
43
+ Both were run red (see below).
44
+ - The star, the stream arrow and the Office "checking" mark are Tabler `star`, `arrow-right` and `refresh` (the path data was compared
45
+ with the upstream files, unchanged). The sprite has twelve symbols of the twenty allowed, and `THIRD_PARTY_NOTICES.md` names all of them.
46
+ - The Office conditions are drawn as shapes (ring, disc, cross, bars, raised hand), each next to its words. The condition names
47
+ (`idle`, `busy`, `offline`, `paused`, `waiting`) are unchanged.
48
+ - Every icon use sits beside a text label or has an `aria-label`; a test counts the `icon()` and `officeIcon()` call sites.
49
+
50
+ ## R5: Office colours and contrast
51
+
52
+ The Office rules already named colours only through `var(--token)`, so nothing was remapped. What changed is the checks:
53
+
54
+ - The colour-literal test no longer skips the Office block.
55
+ - A new test reads the tokens each Office rule uses, fails on a token it does not classify, and checks text tokens at 4.5:1 and mark
56
+ tokens at 3:1 against the three Office surfaces (`--panel-soft`, `--panel`, `--control`), in both themes. The earlier R1 test
57
+ already covered text, `--dot`, `--on-accent`, disabled controls and the peer palette.
58
+ - **Decision to review:** the hairline tokens `--line` and `--control-border` carry no information (room, desk and shelf edges,
59
+ dividers) and measure 1.2 to 1.7:1 on those surfaces, so they are classified as separators and not held to 3:1. If a reviewer wants a
60
+ 3:1 edge, that is a colour change to the tokens, not a test change.
61
+
62
+ ## Checks that were run red
63
+
64
+ Each was applied to the working tree, the tests run, and the file restored:
65
+
66
+ - an Office rule using an unknown token: the new Office token test failed;
67
+ - a `⟳` put back into the page script: the AC7 glyph test failed;
68
+ - the Office `ring` shape renamed in `office-model.ts`: the AC7 shape test failed.
69
+
70
+ ## Not covered
71
+
72
+ - The Office was shot in Flat mode with five idle peers. The busy, offline, paused and waiting shapes, the checking icon and the
73
+ approval hand were checked by the model tests and the DOM tests, not seen in a browser. Isometric was not shot.
74
+ - The "restart pending" badge on a setting row and the Pending restart count need a pending key, and the sample hub had none, so they
75
+ were not rendered. They are covered by the DOM tests only.
76
+ - Memory's long-term and tasks tiers, and the graph with more than one hop, were not shot.
77
+ - Chrome only. Of the attached images, the Message and the dark Office were not looked at one by one.
@@ -0,0 +1,82 @@
1
+ # #469 R6: keyed rendering, quiet motion, and loading, empty, disconnected and toast states
2
+
3
+ Task #174, branch `feat/469-r6-motion` (on top of `feat/469-r4-views`). The poll interval, every action, authority rule and pinned
4
+ element id are unchanged.
5
+
6
+ ## Source
7
+
8
+ A scratch hub outside any git repository with its own `HOME` and `AGENTHUB_HOME` (never the person's home or live hub): five fake
9
+ pull-only peers and eight sample tasks, plus a second hub with no peers and no tasks for the empty views. Headless Chromium over the
10
+ DevTools protocol with `prefers-color-scheme` emulated, at 1440x1000 and 390x900. `/snapshot` was held, failed and released through
11
+ the DevTools Fetch domain to produce the skeleton and the disconnected state.
12
+
13
+ | What | image |
14
+ | --- | --- |
15
+ | Skeleton, board 1440 light / 390 dark | `assets/469-r6-skeleton-1440-light.png`, `assets/469-r6-skeleton-390-dark.png` |
16
+ | Toast stack 1440 light / dark, 390 light | `assets/469-r6-toasts-1440-light.png`, `assets/469-r6-toasts-1440-dark.png`, `assets/469-r6-toasts-390-light.png` |
17
+ | Disconnected 1440 light / dark, 390 dark | `assets/469-r6-disconnected-1440-light.png`, `assets/469-r6-disconnected-1440-dark.png`, `assets/469-r6-disconnected-390-dark.png` |
18
+ | Empty board 1440 light / dark | `assets/469-r6-board-empty-1440-light.png`, `assets/469-r6-board-empty-1440-dark.png` |
19
+ | Empty peers 390 light / dark | `assets/469-r6-agents-empty-390-light.png`, `assets/469-r6-agents-empty-390-dark.png` |
20
+ | Board after load 1440 light, 390 dark | `assets/469-r6-board-1440-light.png`, `assets/469-r6-board-390-dark.png` |
21
+
22
+ ## AC8: keyed rendering
23
+
24
+ How it works: a keyed pass (`keyedStart`) hands out one node per key. A card, peer row or memory row whose JSON signature is the same
25
+ as last time is the same node; a changed one is rebuilt (its listeners close over its data, so it is replaced, not patched) and, if
26
+ it held focus, `update()` hands the focus to the replacement. Keys that are gone are dropped. The card signature includes the
27
+ board context, the capabilities its buttons depend on and the release name, so a change to any of those rebuilds the card.
28
+
29
+ - **vm tests** (`test/dashboard-motion.test.ts`), on the real board code and the real `update()`: after a poll that changes one
30
+ task the other three cards are the same objects, the changed one is new, and a focused card keeps focus; a focused card that
31
+ itself changes passes its focus to its replacement; an unchanged poll leaves `children` as the same array; a gone card is removed
32
+ and the rest are untouched; a card that changes capability is rebuilt.
33
+ - **Chrome**, four theme and size combinations: focus the first card, add a task, wait three polls: `document.activeElement` is
34
+ still that card, it is still connected, and the three cards after it are the same nodes (`3/3`), with the card count up by one.
35
+
36
+ ## AC9: motion, states
37
+
38
+ - **Motion test over the CSS:** every `transition` is `none` or on `transform` or `opacity` only, 120 to 200 ms (the Office's own
39
+ .8 s transform move is pinned elsewhere and allowed up to .8 s); every `@keyframes` moves only `transform` and `opacity`; every
40
+ animation names one of the known keyframes at 120 to 200 ms (the Office ones keep their pinned timings); the only `infinite`
41
+ animation in the page is the Office pulse; and one `@media(prefers-reduced-motion:reduce)` rule turns `transition` and
42
+ `animation` off for `*`, `*::before` and `*::after`.
43
+ - **Chrome with reduced motion emulated:** computed styles at 1440. With no preference: view animation `view-in` 0.16 s, button
44
+ transition 0.12 s, card transition 0.15 s, toast `toast-in`. With `reduce`: all four are `none` or `0s`.
45
+ - **Skeleton:** five column blocks on the board and three bars in the peer list, `aria-hidden`, with the text "Loading the board..."
46
+ next to them. With `/snapshot` held they were on screen; after release `.skeleton` was gone from the page (0 left). They do not
47
+ animate.
48
+ - **Empty views:** the board, peers, stream, approvals, memory (three cases) say what fills them and the command. Every command they
49
+ name is in `ahub help` (a test). The empty hub showed the board and peers texts in Chrome.
50
+ - **Disconnected:** with `/snapshot` failing, `main` gets `data-stale`, its content measured `opacity 0.6`, the status read
51
+ "Disconnected / retrying: Failed to fetch", and the line read "Last snapshot at 10:43:11 PM. Polling continues every second."
52
+ After the fetch came back both were cleared. Seen at 1440 and 390, light and dark.
53
+ - **Toasts:** three notices (one success, two errors) gave a stack of three with a Dismiss button each, the box inside the viewport
54
+ and no sideways scroll (`scrollWidth` equals the viewport). Five seconds later the success had faded and both errors were still
55
+ there; pressing Dismiss on each left none. Test: four at most, the same text replaces its toast, an empty text clears the stack,
56
+ the fade starts at 4 s and the removal 160 ms later, an error never expires, reduced motion keeps a success.
57
+
58
+ ## Pinned contracts changed (named, nothing loosened)
59
+
60
+ - `test/ui.test.ts`: it read `#notice` as one element (`textContent`, `error`). With a stack those two reads come from the newest
61
+ toast, defined once in the harness. Every assertion below is unchanged.
62
+ - `test/dashboard-views.test.ts`: it pinned five `setTimeout(` calls in the page. The five are still there; `notice()` adds two
63
+ (the fade and the removal), and the test now pins both counts, so a timer anywhere else still fails it.
64
+ - The card's own transition was `transform .15s, box-shadow .15s`. It is `transform .15s` now, because the spec limits motion to
65
+ `transform` and `opacity`; the hover shadow still appears, without easing.
66
+ - `update()` gained focus carry-over and `resetView` clears the keyed caches; neither changes the signature fast path.
67
+
68
+ ## Checks that were run red
69
+
70
+ Each applied to the page, the new tests run, the file restored: a cache that never hits; no focus hand-off; a success toast that
71
+ never fades; an error toast that fades; a `width` transition; a weakened reduced-motion rule; an `infinite` card animation; a
72
+ disconnected line never cleared; an animated skeleton; a capability left out of the card signature. Each failed the test written
73
+ for it.
74
+
75
+ ## Not covered
76
+
77
+ - Peer rows and memory rows use the same keyed pass, but only the board's cards were driven through a poll in a browser and in the
78
+ vm tests; the peer and memory paths are covered by the shared pass test and by the existing tests, which still pass.
79
+ - A card that is replaced and was hovered loses its hover until the pointer moves. That is the browser's behaviour for a new node.
80
+ - The card's enter animation and the toast's fade were checked by computed style and by the test over the CSS, not watched.
81
+ - Dragging a card across a poll was not exercised.
82
+ - Chrome only. The dark skeleton, the 390 empty board and the Stream's empty text were not looked at one by one.