@staix/agent-hub 0.25.0 → 0.27.0

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 (87) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +12 -6
  3. package/docs/agent-notes/README.md +12 -0
  4. package/docs/agent-notes/adapters.md +1 -1
  5. package/docs/agent-notes/daemon.md +7 -5
  6. package/docs/agent-notes/tasks.md +7 -6
  7. package/docs/agent-notes/tests.md +1 -0
  8. package/docs/dogfood.md +7 -1
  9. package/docs/operations.md +160 -17
  10. package/docs/profiles.md +1 -0
  11. package/docs/quickstart.md +18 -4
  12. package/docs/sdlc.md +3 -3
  13. package/docs/security.md +52 -9
  14. package/docs/specs/2026-09-19-agent-hub-design.md +1 -1
  15. package/docs/specs/2026-11-01-research-campaigns-design.md +21 -7
  16. package/docs/verification/2026-11-01-0.26.0.md +54 -0
  17. package/docs/verification/2026-11-01-0.27.0.md +49 -0
  18. package/docs/verified.json +28 -16
  19. package/package.json +1 -1
  20. package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
  21. package/plugins/agent-hub/server.js +190 -101
  22. package/src/adapters/channel-peer.ts +4 -2
  23. package/src/adapters/definitions/builtin-metadata.ts +6 -6
  24. package/src/adapters/definitions/qwen.ts +2 -0
  25. package/src/adapters/registry-types.ts +2 -0
  26. package/src/adapters/registry.ts +1 -0
  27. package/src/cli/conductor-recovery.ts +3 -2
  28. package/src/cli/help.ts +9 -8
  29. package/src/cli/identity.ts +5 -5
  30. package/src/cli/main.ts +68 -24
  31. package/src/cli/recovery-package.ts +94 -4
  32. package/src/cli/reset.ts +14 -4
  33. package/src/cli/runtime-version-warning.ts +28 -0
  34. package/src/cli/start-agents-run.ts +125 -0
  35. package/src/cli/study-output.ts +56 -0
  36. package/src/cli/study.ts +26 -3
  37. package/src/cli/task-output.ts +5 -0
  38. package/src/cli/upgrade-interactive.ts +4 -4
  39. package/src/cli/upgrade-runtime.ts +105 -57
  40. package/src/cli/upgrade.ts +90 -28
  41. package/src/hub/action-policy.ts +5 -3
  42. package/src/hub/autonomy-defaults.ts +32 -7
  43. package/src/hub/board.ts +129 -13
  44. package/src/hub/budget.ts +3 -4
  45. package/src/hub/child-process.ts +4 -3
  46. package/src/hub/conductor.ts +3 -4
  47. package/src/hub/config-apply.ts +13 -2
  48. package/src/hub/config-edit-host.ts +1 -0
  49. package/src/hub/config-tier.ts +3 -0
  50. package/src/hub/config-trust.ts +10 -2
  51. package/src/hub/control-client.ts +30 -10
  52. package/src/hub/daemon.ts +103 -26
  53. package/src/hub/db-schema.ts +143 -0
  54. package/src/hub/delivery-journal.ts +3 -4
  55. package/src/hub/events.ts +2 -1
  56. package/src/hub/execution-budget.ts +3 -4
  57. package/src/hub/folder-trigger.ts +3 -4
  58. package/src/hub/hub-tools.ts +7 -5
  59. package/src/hub/invocations.ts +3 -4
  60. package/src/hub/lifecycle.ts +87 -29
  61. package/src/hub/manager.ts +49 -33
  62. package/src/hub/no-writes.ts +1 -1
  63. package/src/hub/orca-env.ts +19 -0
  64. package/src/hub/project-runtime-state.ts +164 -0
  65. package/src/hub/project-runtime.ts +62 -0
  66. package/src/hub/protocol-window.ts +19 -0
  67. package/src/hub/recovery-store.ts +102 -12
  68. package/src/hub/registry.ts +5 -0
  69. package/src/hub/research-experiments.ts +33 -4
  70. package/src/hub/research-runner.ts +172 -0
  71. package/src/hub/restart.ts +9 -0
  72. package/src/hub/runtime-capture-prune.ts +415 -0
  73. package/src/hub/runtime-prune-health.ts +112 -0
  74. package/src/hub/schedules.ts +3 -4
  75. package/src/hub/sdlc-merge.ts +7 -0
  76. package/src/hub/sdlc-queue.ts +13 -1
  77. package/src/hub/sdlc-release-authorization.ts +11 -5
  78. package/src/hub/session-identity.ts +1 -1
  79. package/src/hub/settings.ts +4 -0
  80. package/src/hub/snapshots.ts +3 -4
  81. package/src/hub/start-agents.ts +44 -0
  82. package/src/hub/task-approval.ts +23 -0
  83. package/src/hub/tasks.ts +133 -34
  84. package/src/hub/usage-rollup.ts +3 -4
  85. package/src/pi/tool-receipts.ts +3 -4
  86. package/templates/presets/confirmation-matrix.json +5 -5
  87. package/templates/release-guard.block.md +2 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.27.0
8
+
9
+ - Let the configured conductor record release consent on the person's explicit version-naming instruction (#1063). Receipt and audit retain the real asker; exact candidate/docs/origin bindings, restart revocation and live-test refusal remain.
10
+ - Record inert experiment protocols, plans, attempts, events and signed runner receipts through `hub_experiment_record` (#932, task 615). Existing Tasks authority and verification apply; PII turns refuse, raw arguments/results stay out of memory capture, and the relay starts no experiment.
11
+ - Concurrent `ahub up` cold starts tolerate an atomically replaced runtime record, while malformed, linked and oversized records still refuse (task 616).
12
+ - Read recorded research experiments (task 602, #932 T3): `ahub study experiments --campaign <id>` (or `--attempt`, or `--plan` with `--baseline` and `--variant`) and the read-only `hub_experiment_show` render a campaign's attempts and one comparison. Every value shows whether it is declared, host-observed, runner-attested or person-attested, failed and cancelled attempts stay in the counts, a repeat is never an independent reproduction, and an incompatible comparison is shown, not ranked. A synthetic CPU-only fixture (`test/research-experiments-fixture.test.ts`) covers baseline, variant, failure, changed-protocol, private-input and inaccessible-output cases, and `docs/operations.md` has a person-authored optional rerun checklist. Nothing here runs a command or records an experiment.
13
+
14
+ ## 0.26.0
15
+
16
+ - Retain each project's verified runtime across stop/start, preserve other projects when upgrading one, prune unreferenced source captures with reference checks, and report stopped projects before source preservation (#1039 follow-up). Runtime selection protection begins with the 0.26.0 resolver.
17
+ - Bind signed external runner receipts to research attempts (#1018). Keep runner-attested evidence separate, show current key revocation status, preserve idempotent replay, and guard first-attestation format-2 storage and recovery. Registration is person-only; the hub runs no external job.
18
+ - Re-review approved, unmerged tasks at a new owner-reported head, or let the reviewer withdraw approval (#1047). Keep each verdict bound to its head, show stale approvals, and block stale-head merges.
19
+ - Upgrade one project while other projects keep working (#1039, #1045): negotiate protocol 17/16, use per-project recovery holds and verified versioned daemon captures, and update the global CLI and shared plugin separately. Doctor reports older shared tools.
20
+ - Refuse incompatible `hub.db` formats before daemon effects or database writes, with a named required-version message. Preserve legacy recovery contracts and require guard-aware targets for new controlled replacements.
21
+ - `ahub up` now also starts a default set of agents (task 608, #1040): `claude` and `codex` unless chosen, each through the hub's own start (its start mode, the `terminal.open` or Orca terminal, `already attached` for an attached peer), one line per agent, and a refusal names the command to run by hand without stopping the hub or the others. The set is the new machine-local setting `start.agents` (ignored from a committed `config.json`, a Start-group settings row, an empty list starts none); `ahub setup` asks once (`--agents <a,b>`, `--yes`, no terminal keeps the default) and writes `~/.agenthub/defaults.json`, which `ahub init` copies into a new project; `ahub up --no-agents` skips it for one run. The dashboard's Start counts as `ahub up`. A daemon restart, recovery, config reload or upgrade relaunch starts none from it, and qwen, a declared instance or a channel extra cannot be listed.
22
+ - A test run or `scripts/check.sh` started from an Orca pane no longer opens real Orca tabs and agents there (task 613, #1048). The test preload and the gate now remove every `ORCA_*` variable they inherit, `personEnv` drops the whole prefix, and the launcher's four names live in one list (`src/hub/orca-env.ts`). A test that needs an `ORCA_*` value sets it itself. `test/orca-env-guard.test.ts` runs a real `ahub up` with fake `ORCA_*` values and a stub Orca CLI and fails if the stub is called.
23
+
7
24
  ## 0.25.0
8
25
 
9
26
  - Pin `@staix/agent-mem` 0.18.0 (was 0.17.0), which adds forward-tolerant amem hook-spool handling, an amem console Recap panel and screened tool-input capture. The hub continues to use the library API and does not call the hook spool.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Code, Codex, Kimi Code, Pi and a hub-owned local worker (plus Qwen Code, Jcode a
5
5
  Antigravity if you want them) share a message bus, a task board and a memory, each
6
6
  connected through its own native interface.
7
7
 
8
- Status: 0.25.0, control protocol 16. Bun + TypeScript, one loopback daemon per
8
+ Status: 0.27.0, control protocol 17 (accepts 16). Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
11
  ## Quick start
@@ -14,10 +14,12 @@ You need [Bun](https://bun.sh) 1.4.2 or later on PATH.
14
14
 
15
15
  ```bash
16
16
  bun add -g @staix/agent-hub && ahub setup
17
- cd <your project> && ahub init && ahub up # daemon plus the operator console
17
+ cd <your project> && ahub init && ahub up # daemon, claude and codex, then the operator console
18
18
  ```
19
19
 
20
- Then start the agents you use, one terminal each, and talk to them:
20
+ `ahub up` opens the default agents (claude and codex; choose them at `ahub setup`,
21
+ skip them with `--no-agents`) through `terminal.open` or Orca. Where it cannot, it
22
+ names the command; run the agents you use by hand, one terminal each, and talk to them:
21
23
 
22
24
  ```bash
23
25
  ahub claude # Claude Code with the hub channel
@@ -62,7 +64,8 @@ become links. The scene carries labels and states only, never message bodies or
62
64
  (a planner drafts it with `hub_study_draft`); only the person approves it, which
63
65
  creates one task per unit, and `ahub study show` reads coverage per unit and
64
66
  source. A unit is covered by its owner's `hub_study_handoff` with evidence, not by
65
- a done task.
67
+ a done task. Experiment attempts are recorded with `hub_experiment_record` and read
68
+ with `ahub study experiments`, each value naming where it came from.
66
69
  - **Shared memory.** Decisions, contracts, fails and findings are kept through the
67
70
  first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library,
68
71
  in process with no claude-mem worker (short- and long-term tiers, a typed graph,
@@ -97,7 +100,10 @@ become links. The scene carries labels and states only, never message bodies or
97
100
 
98
101
  ## Upgrading
99
102
 
100
- Run `ahub upgrade` while your hubs are running. Read the notes for your version in
103
+ Run `ahub upgrade` in a project while its hub is running (`--project` selects
104
+ another); other projects keep their version. Update the global CLI
105
+ (`bun add -g @staix/agent-hub@<version>`) and run `ahub setup` for the plugin
106
+ yourself. Read the notes for your version in
101
107
  the [Quickstart](docs/quickstart.md) ("Upgrading to ...") and the
102
108
  [Changelog](CHANGELOG.md) first: some releases need a one-time step, such as
103
109
  refreshing an agent's MCP entry.
@@ -121,7 +127,7 @@ uses the home must run a version that understands the new layout.
121
127
  [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
122
128
 
123
129
  Live checks that still need a real account or a person are listed in the
124
- [release verification report](docs/verification/2026-11-01-0.25.0.md) and the
130
+ [release verification report](docs/verification/2026-11-01-0.27.0.md) and the
125
131
  [smoke checklist](docs/smoke.md).
126
132
 
127
133
  ## Documentation
@@ -65,3 +65,15 @@ Background moved out of the area notes for 0.21.0:
65
65
  - Web fetch transport (#553, tests.md): the daemon test's answer waited on iana.org for 99 percent of its time and failed about 3 of 100 runs at 30 parallel on a 4 s bound; a longer wait would only have measured the network.
66
66
  - Jcode usage (#801, budget.md): Jcode's `totalTokens` adds the cached read on top of an input that already holds it for OpenAI-style providers, and `jcode usage --json` names its limit with free text and no window length.
67
67
  - Claim verdicts (#901, tasks.md): review of #937 found `factCheck.agreement` counting person-verdict frequencies, which is why a person verdict alone is never an agreement.
68
+
69
+ - Registered runner integration (#600, daemon.md): the frozen runner contract narrowed issue #1018 from person-or-conductor registration to person-only trust-root changes. Generic machine-local stripping would silently ignore shared/tracked runner keys instead of refusing them, and recursive declaration discovery omitted whole-map scoped replacements after registration existed. The config/recovery fixtures exercise explicit source refusal, atomic whole-block publication, pending raw edits versus accepted revocation, and the separate recovery floor and integrity binding.
70
+
71
+ Issue #1047 records approved tasks that could not accept a new head or a withdrawn verdict on hub 0.23.1. The prior in-review-only rule left the board bound to an older commit while reviewers handled later PR heads in comments. The approval reopening rules in tasks.md replace that restriction; fixture coverage includes a legacy receipt-only approval, a configured check without a prior reviewer, and a PR merged while a screen is pending.
72
+
73
+ Task 610 follows the staged-upgrade review: ordinary startup forgot the scoped target, scoped refresh replaced the shared manager, verified captures accumulated, and an empty plan reached source preservation before its not-running refusal. Runtime selection now starts at the first resolver release (0.26.0); earlier published binaries do not read the pointer, so that ceiling is documented rather than enforced by a broader database floor.
74
+
75
+ Review of PR #1050 found that including accepted runner configuration and raw-key presence in release-equality integrity made pending config edits and legacy no-receipt snapshots fail the release step. Runner configuration is mutable startup policy; receipt rows are retained board state. The runner integrity rule now separates those roles, with two-daemon prepare/commit/release fixtures covering both transitions.
76
+
77
+ Review of PR #1052 found that full-index trust sweeps and hashing every retained capture under the registry writer lock made ordinary startup refuse large repositories or starve other project writers. Per-start scoped trust and bounded quarantine transactions separate execution authority and deletion safety from maintenance cost.
78
+
79
+ The ledger review of PR #1052 found that applying the startup admission wait to worker completion could strand the ledger in running state after contention or a crash. Signed process ownership, an atomic dead-owner replacement, a normal worker wait and read-only doctor paths keep deferred maintenance recoverable and visible.
@@ -8,7 +8,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
8
8
 
9
9
  - Keep per-kind native policy in `src/adapters/definitions/` and consume it through the registry. Private CLI implementations accept only fixed host-bound ports; keep identity, audit, lifecycle admission and effect ordering in the host. Except for the verified person-owned A4 startup-module contract, do not add config-selected callbacks or runtime imports from registry leaves into eagerly initialized hub/CLI modules; use type-only ports for existing host planners.
10
10
  - Adapter registry (#451): kind-specific policy lives in that peer's `AdapterDefinition` under `src/adapters/definitions/` (runtime definitions `NATIVE_BUILTIN_DEFINITIONS_WITHOUT_QWEN` in `builtin-metadata.ts` plus `qwen.ts`; the daemon constructs every native driver through `start.create` and `DAEMON_PORTS`). Outside `src/adapters/`, read a semantic field of `definitionOf(id)`: never a raw peer-id comparison, a resolved kind (`kindOf`, `definitionOf(...).kind`) or a constant alias compared with a native name, a vendor-keyed object, a peer-name array, or a closed-list import from `peer-catalog.ts`, `start-mode.ts` or `permission-mode.ts`; the guard resolves aliases and import/re-export bindings. `test/registry-guard.baseline.json` is `[]` and stays empty: a new site moves into a definition, never into the baseline. The only exceptions are the cross-release literals `KNOWN_PEERS`, `OPTIONAL_PEERS` and `START_MODE_PEERS`, which `test/registry-guard.test.ts` holds equal to the derived lists. `LaunchContext.nativeCommand` carries only validated config argv (or its redacted preview), never model input; a definition's `permission.grants` text is what `permissionGrant` shows, ahead of the built-in wording. Keep native mode-id maps in `definitions/*-native.ts`, free of registry factories (`test/adapter-import-order.test.ts`).
11
- - Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, `session.nativeRevert`, a `pi-rpc` or `claude-implementer` daemon port, `identity.currentChild`, `routing`, `projection`, `reviewerVariant`, `headlessVariant`, `reviewer`, `hooks.factsHook`, `cli.entry`, `cli.preview`, `cli.usage` or `instances.modelSelect`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra with `start_mode: "headless"`, roles exactly `["reviewer"]`, and either a variant whose `reviewer.declaration` is `"automatic"` (Claude, agy) or `review_mode: "fresh"` (Codex, Kimi); without that opt-in a reviewer-only Codex or Kimi instance keeps its native adapter. It picks a kind's `headlessVariant` (Claude, #525-4) for a declared headless extra whose roles include `implementer` and hold only `implementer` and `verifier`. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. A new field that depends on fixed-builtin assumptions or host-global native state joins that list (`test/adapter-capability-scope.test.ts`). `trust.onPrem` admits PII work (rule: `tasks.md`).
11
+ - Registry load runs `assertDaemonEngine` and `assertNativeCapabilityScope` before publication. A definition that is not a fixed builtin (object identity in `fixedBuiltinDefinitions`, never a kind name) may not declare `start.acpCommand`, `start.upStart`, `attach.admission`, `budget.resetSoftTokensOnResume`, `session.crashIdentity`, `session.nativeRevert`, a `pi-rpc` or `claude-implementer` daemon port, `identity.currentChild`, `routing`, `projection`, `reviewerVariant`, `headlessVariant`, `reviewer`, `hooks.factsHook`, `cli.entry`, `cli.preview`, `cli.usage` or `instances.modelSelect`. Only `definitionOf(id, instances)` picks a kind's `reviewerVariant`, for a declared extra with `start_mode: "headless"`, roles exactly `["reviewer"]`, and either a variant whose `reviewer.declaration` is `"automatic"` (Claude, agy) or `review_mode: "fresh"` (Codex, Kimi); without that opt-in a reviewer-only Codex or Kimi instance keeps its native adapter. It picks a kind's `headlessVariant` (Claude, #525-4) for a declared headless extra whose roles include `implementer` and hold only `implementer` and `verifier`. Metadata-only reviewer variants never establish native peer support; `isSupportedPeer` shares the registry runtime/validated-instance predicate with the catalog and daemon. A new field that depends on fixed-builtin assumptions or host-global native state joins that list (`test/adapter-capability-scope.test.ts`). `trust.onPrem` admits PII work (rule: `tasks.md`).
12
12
  - Native branch ratchet (#625): `test/daemon-native-branches.test.ts` counts every call of a native class predicate (an `is*Peer` imported from an adapter module other than `registry.ts`) per enclosing function in `src/hub/daemon.ts` and `src/hub/claude-launch-proof.ts`, and the counts must equal `test/daemon-native-branches.json` (`predicate`, `function`, `count`, `reason`). A PR that adds, moves or removes such a call edits its row by hand with a concrete reason; never regenerate the table from a test. Bind a predicate alias with `const` only and never call one through a computed namespace key: the scan refuses both.
13
13
  - Native proof state (`frontendProxy`, builtin `statusNotice`, `revertTurn` with `session.nativeRevert`, the Claude launch proof and terminal execution root) is read in the daemon only after `isFixedBuiltinDefinition(definitionOf(peer))`: a same-name foreign definition gets none of it. A product of a definition that is not a fixed builtin may not have a `frontendProxy`, `statusNotice` or `revertTurn` member at all, even undefined (`assertNativeProofPorts`, run by `createFromDefinition` and the conformance kit). Use `frontendProxyState` for the daemon proxy reads: absence keeps the generic path, present undefined refuses with `frontend proxy state unknown`. Keep approval policies and restoration debt inside CodexPeer. The remaining Codex `actionEnforcement`/`actionGated` predicate is intentional until a separately specified action-policy port; never add it to `FrontendProxyPort`.
14
14
  - Adding a native peer: declare its `AdapterDefinition` under `src/adapters/definitions/` (or its own directory, as agy) and list it in `ADAPTERS` (`src/adapters/registry.ts`). `KNOWN_PEERS`, `OPTIONAL_PEERS`, `SUPPORTED_PEERS`, `START_MODE_PEERS` (a kind with both a TUI and a headless form), `ROLE_ORDER`, `HELP_ORDER` and `PLANNER_PEERS` are derived from it by `deriveAdapterLists`: set `optional`, `projection` (`roleRank`, `startRank`, `helpRank`, `plannerEligible`; a configured or external definition cannot) and, for an `ahub <kind>` launcher, `cli.entry`, `cli.usage`, `cli.preview` and `cli.startWords`, never a list edit (`builtinCliEntries(definitions)` projects the entries). The literal cross-release copies `KNOWN_PEERS` and `OPTIONAL_PEERS` in `src/hub/peer-catalog.ts` and `START_MODE_PEERS` in `src/hub/start-mode.ts` stay hand-kept for older coordinators: `test/registry-guard.test.ts` fails when one differs from the derived list. Adding a peer is the one change that updates their probe counts and `VENDORS` in `test/helpers/registry-inventory.ts` (`REGISTRY_COMPATIBILITY_PROBES`), the probe literal in `test/adapter-registry-a2.test.ts`, the `ADAPTERS` order in `test/adapter-registry.test.ts` and the literal in `test/adapter-catalog-a2.test.ts`; say so in the PR as an expected test change. `isSupportedPeer` answers from `ADAPTERS`, and for a declared instance from its kind's runtime definition unless the instance resolves to an unavailable reviewer variant; `isKnownPeer` and `defaultPeerRoles` answer from `definitionOf`; `STARTABLE_PEERS` reuses `SUPPORTED_PEERS`. It also adds the kind's `kinds` entry and its rows to `templates/presets/confirmation-matrix.json` (#999 T0): `test/confirmation-matrix.test.ts` fails without a tool-approval and a classed-action row per environment, and when the entry's forms, pull-only forms, conductor start or hub-mode fact differ from the definition. Add the same kind to `kinds` in `templates/presets/autonomous.json`, with a permission per form (`null` where the hub mode is `unmanaged`, or for `tui` under `unmanaged-in-tui`) and a start per environment: `test/autonomy.test.ts` fails when the preset and matrix kinds differ or a mode is unsupported.
@@ -18,7 +18,7 @@
18
18
 
19
19
  - Console attention navigation selects a delivery only by an exact visible row id. A missing id or row prints read-only guidance; never select by peer or fall back to the first Queue row.
20
20
 
21
- Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/config-tier.ts`, `src/hub/config-edit.ts`, `src/hub/config-edit-host.ts`, `src/hub/claude-launch-proof.ts`, the session and provenance modules (`src/hub/session-identity.ts`, `src/hub/provenance.ts`, `src/hub/channel-project.ts`), `src/hub/recovery-continuity.ts`, `src/cli/recovery-presentation-options.ts`, `src/cli/setup.ts`, `src/cli/upgrade*.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts`, `src/cli/facts-hook.ts`, `src/hub/usage.ts`, `src/hub/memory-profile.ts`, the memory code modules (`src/hub/memory-symbols.ts`, `src/hub/memory-code-overlay.ts`, `src/hub/memory-code-head.ts`, `src/hub/memory-stale-anchors.ts`, `src/cli/memory.ts`), the task brief host (`src/memory/brief.ts`, `src/hub/memory-plan-guard.ts`), `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, the acon plugin (`src/ui/plugin.ts`, `src/ui/office-space.ts`, `src/cli/acon-register.ts`), `ahub study` (`src/cli/study.ts`, `src/cli/study-output.ts`), `src/hub/task-code-group.ts` (dashboard Code group), the agent drift sweep (`src/hub/agent-drift.ts`, `src/cli/agents.ts`) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
21
+ Scope: the operator console, control WS and its protocol, state files, the dashboard listener and settings, the Claude channel's reconnect, `ahub setup`, git snapshots, upgrade and recovery. Read before editing `src/adapters/definitions/native-recovery*.ts`, `src/adapters/definitions/native-policy-aux.ts`, `src/adapters/definitions/native-config-*.ts`, `src/adapters/definitions/native-cli-*.ts`, `src/cli/console.ts`, `src/cli/console-state.ts`, `src/cli/attention.ts`, `src/ui/needs.ts`, `src/hub/daemon.ts`, `src/hub/ui.ts`, `src/hub/settings.ts`, `src/hub/control-client.ts`, `src/adapters/claude-channel.ts`, `src/hub/restart.ts`, `src/hub/recovery-store.ts`, `src/hub/snapshots.ts`, `src/hub/manager.ts`, `src/hub/lifecycle.ts`, the runtime selection and retention modules (`src/hub/project-runtime-state.ts`, `src/hub/project-runtime.ts`, `src/hub/runtime-capture-prune.ts`, `src/hub/runtime-prune-health.ts`, `src/hub/registry.ts`), `src/hub/crash.ts`, `src/hub/start-mode.ts`, `src/hub/terminal-open.ts`, `src/hub/recovery-error.ts`, `src/hub/db-timeout.ts`, `src/hub/db-schema.ts`, `src/hub/protocol-window.ts`, `src/hub/config-reload.ts`, `src/hub/config-apply.ts`, `src/hub/config-tier.ts`, `src/hub/config-edit.ts`, `src/hub/config-edit-host.ts`, `src/hub/claude-launch-proof.ts`, the session and provenance modules (`src/hub/session-identity.ts`, `src/hub/provenance.ts`, `src/hub/channel-project.ts`), `src/hub/recovery-continuity.ts`, `src/cli/recovery-presentation-options.ts`, `src/cli/setup.ts`, the default agent set (`src/hub/start-agents.ts`, `src/cli/start-agents-run.ts`), `src/cli/upgrade*.ts`, `src/cli/runtime-version-warning.ts`, `src/cli/terminal-recovery.ts`, `src/cli/recovery-package.ts`, `src/cli/facts-hook.ts`, `src/hub/usage.ts`, `src/hub/memory-profile.ts`, the memory code modules (`src/hub/memory-symbols.ts`, `src/hub/memory-code-overlay.ts`, `src/hub/memory-code-head.ts`, `src/hub/memory-stale-anchors.ts`, `src/cli/memory.ts`), the task brief host (`src/memory/brief.ts`, `src/hub/memory-plan-guard.ts`), `src/hub/native-hook-launch.ts`, `src/cli/output.ts`, `src/ui/index.html`, the acon plugin (`src/ui/plugin.ts`, `src/ui/office-space.ts`, `src/cli/acon-register.ts`), `ahub study` (`src/cli/study.ts`, `src/cli/study-output.ts`), `src/hub/task-code-group.ts` (dashboard Code group), the agent drift sweep (`src/hub/agent-drift.ts`, `src/cli/agents.ts`) or the instance modules (`src/hub/instances.ts`, `src/hub/instance-config.ts`, `src/hub/instance-execution.ts`), or before adding a native peer.
22
22
 
23
23
  - The plugin bundle is installed apart from the daemon. Breaking a control WS message contract bumps `PROTOCOL` in `control-client.ts`; a new request answered as unknown or an optional field preserves it, as does additive optional public task metadata in `ui_snapshot` that older clients ignore (such as `deps` in #246).
24
24
  - `hub_invoke` over the control WS (#445, #991): the daemon binds each admission to its socket and `rid` with an `AbortController` (`invocationRequests`, at most 64 per connection; a duplicate `rid` refuses). `request_cancel` and socket close abort it, and every check after an await confirms the socket is open and still maps that `rid` to the same controller before any effect. Clients go through `ControlClient.request`, whose `controlRequestTimeout` raises a `hub_invoke` wait to `INVOCATION_CONTROL_TIMEOUT_MS`; never call it with a shorter bound.
@@ -44,6 +44,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
44
44
  - `pet` is console-local presentation (#483): validate it only through `validatePetConfig`, which both `loadConfig` and the reload schema call, and keep it applied live with no authority (`authorizeConfigLeaf`).
45
45
  - Start modes (#269): every start the hub makes itself goes through `hubStart` (`src/hub/daemon.ts`): the dashboard's `start_peer`, the console-role `peer_start`, the conductor's `start` hook and `pi.auto_start`. It takes the mode from `startMode(peer)` and nothing else; never add a caller-chosen mode, flag or command to it, and never fall back to headless when a terminal cannot be opened (refuse with the manual command). The words a terminal runs are assembled there from `process.execPath`, the CLI entry, the project root and `startWords(peer)`. The only continuity exception is a validated Pi bookmark on `pi.auto_start`: append its recorded session file (or its session id when no file was recorded), backend and model; never pass both session selectors or accept caller-written replacements. Recovery and `startPeer` callers that pass a recorded mode keep it. A launcher with no `--mode` takes the daemon's accepted start mode from `status.json` `startModes` (`readControl`) before the file's value. A TUI start of an `owned-acp` peer (Jcode) from the console's `peer_start` opens its terminal even while the ACP owner is online: that terminal is a frontend on the owner. The conductor and the dashboard answer `already` for an attached one (#559). The real terminal providers are wired only in `src/hub/lifecycle.ts` (`DaemonOptions.terminal`): a daemon built without them, as in every test, opens nothing; a test injects a recording provider and never the real one.
46
46
  - A headless start that `hubStart` made for a peer with `capabilities.watch` (Kimi, Qwen) also opens `ahub watch <peer>` through `openWatchPane` (#286 AC10): the same provider, fixed words from `process.execPath`, `CLI_ENTRY`, the project root and the peer id, one per peer per `PEER_START_RETRY_MS`. It runs only after the start succeeded and never fails it: no provider or a failed open reports `watch.command` in the result and a notice, with the provider's own text in the log only. Never open a pane for an `already` result or a declared instance.
47
+ - Default agent set (#608): `start.agents` is read only by the CLI at a person's `ahub up` and by the dashboard manager's start (`afterStart` in `src/hub/manager.ts`), through the console `peer_start` and so through `hubStart` (`src/cli/start-agents-run.ts`). The daemon never reads it: do not add a start from it to daemon startup, restart, recovery, `reloadConfig` or upgrade, and keep the `AGENTHUB_RECOVERY_OPERATION`, `--live-test` and console-caller guards on the `up` body together (each overlaps another, and removing one is caught only because the test removes them all). It is in `ALL_MACHINE_LOCAL` with an empty list kept as a value (`EMPTY_IS_VALUE` in `config-trust.ts`): an empty list means start none, never the default. `src/hub/start-agents.ts` and `config-apply.ts` stay free of file-system imports because the schema reaches the Claude channel bundle. `defaults.json` gained `start.agents`; `writeAutonomyDefaults` and `writeMachineStartAgents` each keep the other half, and any other key still makes the whole file untrusted.
47
48
  - `ahub setup` reads Claude Code's state from `--json` listings and takes one step at a time, re-reading after each; never match paths or names by substring.
48
49
  - A copy of the git index keeps the original's mtime (`snapshot()`): git's racy-entry check compares entries with the index file's time in whole seconds, and a fresh copy makes a same-size edit look clean.
49
50
  - State files that clients read (`status.json`, `control-token`) are written after the port is bound, and `status.json` via temp file + rename.
@@ -56,7 +57,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
56
57
  - Adding a native peer requires testing the command emitted by the actual recovery driver, not only its inspection or launch helpers. Never let a generic non-Codex branch treat a new peer as Claude.
57
58
  - Cross-version recovery must use the authenticated source protocol for prepare/commit/abort and the target protocol after startup. Prove the transition against a real prior release before claiming compatibility.
58
59
  - Preserve `task_cancel` target-feature preflight before source holds, capture or commit. Reuse stored target features only after verifying the same retained root and digest; legacy receipts still capable of commit must obtain feature proof. Do not apply pre-commit compatibility checks to sources already past commit. Every prepare, including the lease renewal before a terminal close and after a resume, sends the operation's stored `targetFeatures` (#478).
59
- - A recovery target-feature floor (`task_cancel`, `invocation` while the invocation ledger has an open (`queued` or `delivered`) row, `peer_instances`, `role_stages` while a declared Claude-kind instance holds `implementer`, `task_planners` while a list/instance planner is configured or `Board.hasPlannerState()` finds a task/release binding or plan-review authorship marker, `release_trains` while a configured train is active or frozen, `reviewer_pool` while a fresh reviewer or `review.pool` is configured, `configured_acp` while the project declares `adapters`, `external_adapter_modules` while external module definitions are loaded, `external_adapter_instances` with `external_adapter_modules` and `peer_instances` while an external module-kind instance is declared, `conductor_recovery` while `Board.hasConductorManualState()` finds open conductor manual-assignment intent, `project_autonomy` while the daemon holds retained autonomy consent or a raw `config.json` or `config.local.json` has a top-level `_autonomy` key (an unreadable file refuses as unknown), `calendar_schedules` while a raw `schedules` entry has a `cron` or `tz` key (#1017: an older target refuses the key at load), `research_records` while `Board.hasResearchState()` or a raw `config.json` or `config.local.json` `research` object names `runners` (unreadable refuses as unknown), `research_dispatch`, `research_dropped_offers`, `research_experiments` and `research_comparisons` while `Board.hasResearchDispatchState()`, `hasResearchDroppedOffers()`, `hasResearchExperimentState()` and `hasResearchComparisonState()` find rows (each research digest joins `integrity()` only when its rows exist), `action_policy_floor` while the raw `config.json` or `config.local.json` names `action_policy.floor`, true or false, even where the key is ignored or normalized away: an older target parses the raw key and rejects it before any trust rule, so check raw file presence, never the effective config (#582, seen twice)) lives in `incompatibleRecoveryTarget`, which every existing check site calls, and its name in `DAEMON_FEATURES`; never check a floor at one site only.
60
+ - A recovery target-feature floor (`task_cancel`, `invocation` while the invocation ledger has an open (`queued` or `delivered`) row, `peer_instances`, `role_stages` while a declared Claude-kind instance holds `implementer`, `task_planners` while a list/instance planner is configured or `Board.hasPlannerState()` finds a task/release binding or plan-review authorship marker, `release_trains` while a configured train is active or frozen, `reviewer_pool` while a fresh reviewer or `review.pool` is configured, `configured_acp` while the project declares `adapters`, `external_adapter_modules` while external module definitions are loaded, `external_adapter_instances` with `external_adapter_modules` and `peer_instances` while an external module-kind instance is declared, `conductor_recovery` while `Board.hasConductorManualState()` finds open conductor manual-assignment intent, `project_autonomy` while the daemon holds retained autonomy consent or a raw `config.json` or `config.local.json` has a top-level `_autonomy` key (an unreadable file refuses as unknown), `calendar_schedules` while a raw `schedules` entry has a `cron` or `tz` key (#1017: an older target refuses the key at load), `research_records` while `Board.hasResearchState()` or a raw `config.json` or `config.local.json` `research` object names `runners` (unreadable refuses as unknown), `research_dispatch`, `research_dropped_offers`, `research_experiments` and `research_comparisons` while `Board.hasResearchDispatchState()`, `hasResearchDroppedOffers()`, `hasResearchExperimentState()` and `hasResearchComparisonState()` find rows (each research digest joins `integrity()` only when its rows exist), `research_runners` under the runner registration rule below, `action_policy_floor` while the raw `config.json` or `config.local.json` names `action_policy.floor`, true or false, even where the key is ignored or normalized away: an older target parses the raw key and rejects it before any trust rule, so check raw file presence, never the effective config (#582, seen twice)) lives in `incompatibleRecoveryTarget`, which every existing check site calls, and its name in `DAEMON_FEATURES`; never check a floor at one site only.
60
61
  - Project autonomy (`autonomyCommand` in `src/hub/daemon.ts`, `writeAutonomyPreset` in `src/hub/settings.ts`): the `autonomy` control message is console-only; its operations come only from `planAutonomy` and `autonomyRemovalKeys`, never from the request; `off` removes the fixed key list and clears consent whatever `_autonomy` says. `_autonomy` is inert metadata, never approval provenance. Retained consent is created only by `createAutonomyConsent(...).confirm` inside the daemon (console `on`, or startup under a trusted `readAutonomyDefaults` opt-in with an exact plan match), never by a CLI or offline writer; a config `never-ask` takes effect only for a peer in `autonomyConfirmed`.
61
62
  - Defer hub notices only for an authenticated recovery `prepare` or `commit` whose target features include `recovery_deferred_hub`, or for a restored daemon. Otherwise `setRecoveryHold(true)` moves the deferred notices into queues before commit records expected integrity, and a bare `fenceRecovery` defers nothing. Abort and lease expiry release the bus hold before clearing the operation; if that release fails, keep the operation and let expiry retry on its 1 s timer (#478).
62
63
  - Resume a prepared `commitSent` source that is unavailable or database-busy inside the existing bounded shutdown poll; never re-send commit, re-prepare or close terminals again on that path. Require confirmed `stopped` before target startup, refuse a replacement instance, and retain the prepared receipt on timeout.
@@ -67,7 +68,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
67
68
  - A restored daemon accepts a changed native session id (Kimi and local aside: they restart as fresh workers) in two ways only: the #64 rule for a Claude session that never persisted a transcript, and the operation-fenced `recovery-waivers.json`, which the coordinator writes wherever it accepts a session other than the planned one: a reconnect-only Claude (#206), an operator-chosen fresh session, the plan's `freshStart` of a Codex thread the hub saw start with no turn, or a zero-turn Claude it accepted (#215). Never waive on any other evidence. Re-preparation after an expired lease keeps every effect receipt, and `restored:<peer>` = `failed` never counts as restored. Judge a restoration launcher by its `terminal-recovery.json` record and process signature (and a `terminal_handle_stale` answer), never by the terminal's exit; an unreadable record is unknown, never "none".
68
69
  - A target that stopped after it started is restarted only through `recordRestart` (`src/cli/upgrade.ts`), recorded before the new one starts and at most once per project per resume: each done or waiting `restored:<peer>` binding becomes `retired:<peer>`, whose terminal is closed (`closedRetired:<peer>`) or found absent before that peer is relaunched, and is never revalidated against the new instance. Keep repaired-session provenance across every target restart, disabling automatic fresh/zero-turn replacement. A person may explicitly accept loss of a repaired Codex/Claude conversation only on typed positive missing transcript/rollout evidence and a disposal-time recheck; refuse live-test repaired overrides after read-only receipt classification and before runner/lock or coordinator mutation, fail closed on unclassifiable scope, and repeat in core before lock and after under-claim read, preserving ordinary #408 delegation; record the one-use grant and freshOverride evidence, and consume the grant on exact attachment. Pi stays excluded: positively missing accepted Pi files get stop-and-archive plus the normal project-scoped ahub pi command; bookmark retirement never falls back fresh in the same start. Clear the repair fence only for a done or waiting binding retired from that target; an uncertain creation without a durable binding keeps its fence. Launcher evidence is read for every instance in `restarts`, and a session attached without an id is unknown, never gone (#225).
69
70
  - After a managed terminal is created, persist its binding before waiting up to `recovery.reconnect_wait_s` (#489 H4, read by `readRecoveryWaits` at the start of the wait; unset 600 s) for authenticated attachment to the expected target instance, operation and conversation (#414). Resume waits on the saved binding without creating another terminal. Count only attached, non-offline session ids; when the chosen terminal has no current launch record, a confirmed-ended row for another handle or an id-less attachment retains the bounded wait after older-launcher checks. A late current record must match the saved terminal; unknown or live replacement evidence still blocks. Timeout keeps the receipt blocked and records only terminal/hub metadata; a last native connect attempt that was never recorded is unknown, never inferred from terminal readiness or offline state. Final identity and preservation verification stays strict. `createTerminal` re-reads the launch record and Orca binding of an idle replacement every `MAP_POLL_MS` until it maps to the original peer and session, a known identity or session contradicts it, or the deadline passes; only then does it report `ambiguous-create`. Missing identity alone is never a mapping (#599).
70
- - An operation receipt stays `schema: 1`. Only `abandoned()` writes 2, on an operation with a recorded `disposition` whose coordinator predates #215: in the same write as the disposition, or, for a disposition 0.12.20 recorded at 1, by the next command that holds the runner claim. `validateReceipt` accepts 2 only with a disposition (#227). Never write 2 on an operation that can still be resumed. Disposal clears `error` and `failure`, so each `stop-and-archive` audit entry keeps the operation's `error` at that moment as `cause` (#776): only text the receipt already held, never task or message text.
71
+ - An unscoped operation receipt stays `schema: 1` (a scoped one is written at `schema: 3` and never moves; Staged upgrades below). Only `abandoned()` writes 2, on an operation with a recorded `disposition` whose coordinator predates #215: in the same write as the disposition, or, for a disposition 0.12.20 recorded at 1, by the next command that holds the runner claim. `validateReceipt` accepts 2 only with a disposition (#227). Never write 2 on an operation that can still be resumed. Disposal clears `error` and `failure`, so each `stop-and-archive` audit entry keeps the operation's `error` at that moment as `cause` (#776): only text the receipt already held, never task or message text.
71
72
  - Four checks read another release's source text for an exact line: `coordinatorCurrent` (`export async function disposeRecovery` in `src/cli/upgrade.ts`), `targetReadsWaivers` (`export function readRecoveryWaivers` in `src/hub/restart.ts`), `coordinatorRestarts` (`export function recordRestart` in `src/cli/upgrade.ts`) and `supportsAction` (`export async function reattachRecovery` in `src/cli/upgrade.ts`). Renaming, moving or un-exporting one makes this release read as an older one: keep those lines until a capability list replaces the checks. The reattach CLI dispatch must use the same `supportsAction` check as its printed command; never redirect a fallback command back to a coordinator without that action. Recovery also reads a target's literal `OPTIONAL_PEERS` (`src/hub/peer-catalog.ts`, through `legacyTargetSupport`) and `START_MODE_PEERS` (`src/hub/start-mode.ts`, through `legacyHeadlessArgv`), declared in `src/adapters/definitions/native-recovery.ts`; keep both literal declarations until a published capability replaces them.
72
73
  - Publish terminal ownership only after the current native owner is proven through `terminal_owner` (#482); capture and start acknowledgement are pending evidence only. Preserve unrelated and invalid rows, scope current evidence to project/instance/peer, and recheck the same native generation after asynchronous Orca reads. Keep Claude's hook record pre-spawn. Compatibility and retention ceilings are described in Operations, controlled recovery.
73
74
  - `reattachRecovery` (`src/cli/upgrade.ts`, #463) relaunches only the peer a blocked receipt's `blockedReattachment` names, once per its `reattachments` marker, after the lock, the runner claim and a re-read, and only against the same held target instance and version. The terminal proof is `inspectSavedTerminal` (`src/cli/terminal-recovery.ts`): only an `orphaned` pane without positive connected evidence or `missing` (a typed stale handle and a complete, untruncated inventory) counts as gone; `unknown`, `changed`, an orphaned-but-connected pane or a disconnected-only reading never authorizes a terminal. It counts as reattached only on a `done` restore receipt with the exact recorded session id, session file and project root, then leaves the operation blocked for `recovery resume`. It is person-only: `classifyPeerCommand` and `liveTestRefusal` refuse it from an agent shell.
@@ -137,9 +138,10 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
137
138
 
138
139
  - Campaign dispatch (#597): keep `research_dispatch` additive to `research_records`; any stored campaign requires it, because old campaign stop policies also need current dispatch enforcement. Preserve the conditional dispatch metadata/handoff integrity digest and every other recovery feature guard. A study-dropped owner offer (#603) is a `research_dropped_offers` row written through `Board.recordResearchDroppedOffer` from `Tasks.relevant`, never an in-memory map: a restart between the drop and the approval must still re-offer. It joins `researchDispatchIntegrity` only when rows exist (so a project without one keeps its digest), requires a `research_units` row for its task and a peer-shaped owner, and `studyReoffer` clears it only after the offer is published or the record is stale.
139
140
 
140
- - Experiment storage (#598): preserve `research_experiments` as an additive conditional recovery floor, the experiment-table integrity digest, and all previous research/calendar/autonomy guards. Read check evidence from this project's host reader only; no record input creates a measured field.
141
- - `ahub study` (#601, `src/cli/study.ts`, `src/cli/study-output.ts`): `study.ts` is pure argv parsing (ids `rc-` and twelve hex digits, revisions as positive integers, `--reason` takes the rest of the words) and runs before any connection; `main.ts` reads the contract file against `invokedFrom` with a 64 KiB bound and sends it to the hub, which validates it. `show` is `allowed` in `classifyPeerCommand` through `peerReadForms`; draft, revise, approve, approve-protocol and close are `console`, and the audit label names at most the subcommand (approve-protocol is labelled `study`; a campaign id, a protocol hash and a contract path are the person's text). The default output is for a person and `--json` prints the hub's text byte for byte (`test/output-cli.test.ts`); a new `study` form needs its `--json` branch and a renderer, never a `TEXT_DEBT` entry.
141
+ - Experiment storage and runner registration (#598, #600): preserve `research_experiments`, its conditional integrity digest and every earlier research/calendar/autonomy guard. Read check evidence only through this project's host reader; no record input creates a measured field. Accept `research.runners` only in untracked `.agenthub/config.local.json`; refuse shared or tracked raw keys even when empty. Keep registration person-only, with whole-block tier2 structural edits and atomic scoped reload; never admit conductor writes. Consume registrations through `TasksDeps.researchRunners`, revalidating the active accepted configuration and current source trust each call; never adopt raw disk edits through that getter. While stored runner state or an active/raw runner key exists (empty counts), require `research_runners`. Include `researchRunners` in release-equality integrity only when stored receipts exist, using their count and stored-row digest alone; never bind accepted registrations or raw config presence to board-state equality across restart.
142
+ - `ahub study` (#601, `src/cli/study.ts`, `src/cli/study-output.ts`): `study.ts` is pure argv parsing (ids `rc-` and twelve hex digits, revisions as positive integers, `--reason` takes the rest of the words) and runs before any connection; `main.ts` reads the contract file against `invokedFrom` with a 64 KiB bound and sends it to the hub, which validates it. `show` and `experiments` (#602: `--campaign`, `--attempt` or `--plan --baseline --variant`, the same read as `hub_experiment_show`) are `allowed` in `classifyPeerCommand` through `peerReadForms`; draft, revise, approve, approve-protocol and close are `console`, and the audit label names at most the subcommand (approve-protocol is labelled `study`; a campaign id, a protocol hash and a contract path are the person's text). The default output is for a person and `--json` prints the hub's text byte for byte (`test/output-cli.test.ts`); a new `study` form needs its `--json` branch and a renderer, never a `TEXT_DEBT` entry.
142
143
 
143
144
  - Experiment comparisons (#599): preserve the separate conditional `research_comparisons` floor and integrity for plans, exact protocol/evaluator approvals and causal protocol-context rows. Never backfill legacy order from timestamps or carry approval to a changed hash. Keep the approval op under the console-only `study_` dispatch guard.
144
145
  - Doctor memory capture (#1008, #1025): the `memory capture: <tool>` rows come only from `amem setup <tool> --json`, a preview that writes nothing (`readMemorySetup` in `src/cli/main.ts`: no shell, 10 s, 256 KiB), judged by the pure `nativeMemoryCaptureRow` (`src/adapters/definitions/native-cli-doctor.ts`). A plan not in the documented shape, a non-zero exit, a timeout or unparsable output is unknown, never ready; never read dotfiles text or a client's config, and do not scrub the caller's identity before the call.
145
146
  - Handoff transport (#604): `handoffOut` connects to the target as a `console` role through `ControlClient` (same token file and machine user as every control message; the hello checks the target's project root and instance), and the target answers every handoff message, an unknown one included. `handoffNotifyOrigin` hangs off `board.onChange` inside its own try, so a task move never waits for, or fails because of, another project's daemon. A target is judged by its own `status.json` features (`checkTarget`), never by a guess from its version. The inbound `task_handoff` is refused while recovery is active, counts in `taskOpsInFlight` so a recovery commit waits for it, and hands `handoffIn` a fence (recovery or `stopping`) that it asks again right before the board write, after the PII screen's await. Keep `task_handoff` in `DAEMON_FEATURES` and its recovery guard beside `research_experiments`; no `PROTOCOL` bump, because a new message type is answered as unknown by an older hub and an optional field is ignored.
147
+ - Staged upgrades (#1039): retain `status.protocol` at the compatibility floor and publish `protocolMin`/`protocolMax`; negotiate the highest common control version and preserve each attached channel's actual negotiated protocol in recovery metadata. New scoped recovery receipts use schema 3 and per-project holds; keep legacy unscoped receipt behavior. Start ordinary project daemons from verified versioned release captures. Check `hub.db` with `db-schema.ts` before startup effects and every writable opener; advance its marker only with an incompatible format migration, never merely on opening with a newer binary. Before source replacement, controlled targets require `schema_guard`; preserve the retained target evidence of fully committed legacy receipts. Unguarded published binaries remain outside the bootstrap guarantee. Keep project runtime selection in bounded project-bound `runtime.json`; refuse Git-tracked or indeterminate repository state before using or replacing that authority. Updated 0.26+ up/manager/dashboard paths use its verified capture or a named version refusal. Preserve the explicit pre-0.26 published-CLI ceiling with a `ponytail:` note; never raise a DB/bootstrap floor for this selection. Compute path-scoped Git trust once per startup outside the writer lock; reuse its project-bound access for runtime reads and writes. Record selection and synchronous launch under the same registry lock used for capture publication and prune quarantine; confirm the unchanged selection and owner at readiness, and require the internal daemon to execute from that exact root. Scoped recovery must not refresh the shared manager. Prune only verified unreferenced versioned source captures, keep newest three/version, and defer on active recovery, unknown reference inventory or exhausted budgets. Schedule rate-limited maintenance after startup returns; keep only bounded metadata validation and atomic quarantine inside the registry writer lock, with candidate hashing and recursive deletion outside it. Refuse managed execution from reserved `.runtime-prune-*` paths; leave changed quarantine contents undeleted. Keep the 1 ms ledger wait on startup admission only; worker statements use the normal bounded wait. Record PID plus process signature and replace only a proven-gone running owner atomically under the admission transaction, with id/phase-fenced completion. Doctor names uncertain ledger state and leftover quarantine paths without repairing or deleting them. Reject empty recovery plans before source preservation.
@@ -1,6 +1,6 @@
1
1
  # Task board and hub tools
2
2
 
3
- Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/main-checkout.ts`, `src/hub/checks.ts`, `src/hub/task-repository.ts`, `src/hub/review-findings.ts`, `src/hub/review-scope.ts`, `src/hub/plan-conformance.ts`, `src/hub/sdlc-metrics.ts`, `src/hub/sdlc-merge.ts`, `src/hub/sdlc-mirror.ts`, `src/hub/no-writes.ts`, `src/hub/workflow.ts`, `src/hub/deliverables.ts`, `src/hub/deliverable-files.ts`, `src/hub/research-campaign.ts`, `src/hub/research-experiments.ts`, `src/hub/ci-watch.ts` or the cross-project handoff (`src/hub/handoff.ts`, `src/cli/handoff-args.ts`, `src/cli/handoff-output.ts`).
3
+ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Read before editing `src/hub/tasks.ts`, `src/hub/board.ts`, `src/hub/routing.ts`, `src/hub/task-sweep.ts`, `src/hub/settled-roles.ts`, `src/hub/conductor.ts`, `src/hub/supervision.ts`, `src/hub/tool-refusal.ts`, `src/hub/hub-tools.ts`, `src/hub/review-brief.ts`, `src/hub/reviewer-pool.ts`, `src/hub/reviewer-pool-config.ts`, `src/hub/release-admission.ts`, `src/hub/main-checkout.ts`, `src/hub/checks.ts`, `src/hub/task-repository.ts`, `src/hub/review-findings.ts`, `src/hub/review-scope.ts`, `src/hub/plan-conformance.ts`, `src/hub/sdlc-metrics.ts`, `src/hub/sdlc-merge.ts`, `src/hub/sdlc-mirror.ts`, `src/hub/no-writes.ts`, `src/hub/workflow.ts`, `src/hub/deliverables.ts`, `src/hub/deliverable-files.ts`, `src/hub/research-campaign.ts`, `src/hub/research-experiments.ts`, `src/hub/research-runner.ts`, `src/hub/task-approval.ts`, `src/hub/ci-watch.ts` or the cross-project handoff (`src/hub/handoff.ts`, `src/cli/handoff-args.ts`, `src/cli/handoff-output.ts`).
4
4
 
5
5
  - Scope every task-place comparison through `TaskRepositories` (`src/hub/task-repository.ts`, #476): explicit `refs.repo`, else a common outside-absolute-ref repository or parent, else this project. Preserve relative display spelling within that key and gate overlap, contradiction, replay and held evidence by equal keys. Warm/cache repository identity at task mutation and startup; never repeat Git reads for unchanged overlap inputs. Set `refs.repo` for work in another repository; repo-only accept metadata never grants execution or file access.
6
6
  - Tool callers are models: MCP `inputSchema` is not enforced on the way in. Normalize at the boundary (`cleanRefs`) before anything reaches the board, and never throw after a board write: a publish or notify that follows a `review()` or `done()` board write goes through the one `releaseNotice` guard, which turns a failed publish into a console notice, drops a failed notify and never retries (#231, #243). An assignment's delivery once its board write is saved (#276, #297): the move is fact, the failed publish is not retried in place (the journal's own recovery covers a storage error, and a retry may duplicate a publish that actually left), and a person looks for a queued or needs_review delivery with `ahub queue list --peer <owner>` (then `ahub queue show <id>`, or the accepted entry in `ahub task show <id>` if the journal is down) before re-sending with `ahub task assign <id> <owner>`. The distinction is made once, in `assignOwner`: a failure after the write prints the one console notice from `undeliveredNotice` right there, once, and throws `AssignmentUndeliveredError` carrying the saved task; callers (`review()`'s repeated rejection, `escalate` and its callers, the ready offer, both relays, the idle sweep, rescreen) decide control flow by the type and print nothing more, and `propose`, `assignTo` and `decline` additionally let the error reach the caller in its turn, which is a person or peer that can act. Two exceptions get no notice, by construction: a row superseded meanwhile, meaning a later history entry that moves or settles the task (another assignment, an unassign, the owner's accept, done, approval, close), while entries that do neither (the hub's quiet bookkeeping: answers, reviewer changes, idle sweeps, check results) leave the assignment undelivered and the notice stands, so the original error goes to the caller as before only on supersession; and a claim's own accepted write and "claimed by" line stay outside the delivery rule (a claim is its own hand-over; its overlap and routing announcements to earlier owners are deliveries and share the wrapper, so a failure there does read as one). A failure before the write stays a plain `Error` (#254), a failed write leaves the ready offer unconsumed (#231), and the notices before the write (waits, no-owner, the reserved-owner line) go through `releaseNotice` too, so those pre-write notices cannot fail their caller.
@@ -11,7 +11,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
11
11
  - `assign()` never defaults to the task's current owner, or a decline can only come back to the decliner. A reservation (#207) is the same risk: the reserved owner goes first only through `blocked()`, and a decline or an escalation for reason `rejections` or `manual` records the owner it left in the history entry's `from`, which `excluded()` reads, so no later reroute hands the task back through its reservation.
12
12
  - A named owner that `assign()` refuses is never a silent no-op (#601): `assignTo` passes `refuseNamed` so `assignOwner` throws with the routing refusal reason before any write (pre-effect for tool callers, a plain `Error` for the console), and a proposal's unmet named owner is reported in the propose result through `namedOwnerNote`, read from the stall the proposal recorded. The task stays where it was; never deliver to an offline peer to make the name stick.
13
13
  - Write-class owners (#735): `ownerRoleOfClass` (`src/hub/routing.ts`) is the one derivation of the role that owns a class (`plan` planner, `review` reviewer, everything else implementer); the role ceiling and the owner-admission gate both use it, so never test a fixed list of class names; `explain` and `assign` stay the same function.
14
- - The state machine allows `in_progress -> approved` only for classes without a reviewer; `review()` checks `in_review` itself.
14
+ - The state machine allows `in_progress -> approved` only for classes without a reviewer. Reopening an approval follows the owner/reviewer rules below.
15
15
  - Treat `cancelled` as closed through `isClosedTaskState`, but keep dependency release restricted to approval. Cancellation goes through `Tasks.cancel`, preserves the row and roles, clears its reservation/cohort, sends revision-bound stop notices and writes no outcome memory; never map it to approval or cancel a native turn or worktree. Planner cancellation stays limited to its own proposed/backlog work with no execution evidence (`taskHasWorkEvidence`, the `hub_backlog_return_untouched` test, #812 AC5): an accepted-then-declined untouched task is cancellable, anything with a branch, commit, PR, local check, done, verdict or reopen is person-only and keeps the refusal text. The configured planner may `hub_task_show` any non-backlog task through the conductor's public view (`ConductorHooks.isPlanner`); a staged backlog row stays hidden from agent task tools (#263) and a peer holding both roles takes the audited conductor path. That read grants no hold or assignment.
16
16
  - Capture `TaskOffer.pullOnly` and the exact offer envelope id at publication. Keep unread-offer attention independent of attachment and restart until acceptance, decline, supersession or `offerRead` evidence; never replace that evidence with a generic inbox timestamp or completion state.
17
17
  - Use the daemon's settled role map (`resolveSettledRoles`, `src/hub/settled-roles.ts`, the same resolver reload compares holders with) for planner authority and notice recipients. An explicit planner designation is exclusive; automatic fallback remains runtime-only. Keep ready-unassigned sweep admission consistent with person-unassignment suppression, planner-pull reservation/WIP limits and ordinary routing guards; persist each ladder step before its notice.
@@ -54,7 +54,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
54
54
 
55
55
  - Resolve any id that may be an extra instance or a configured kind through the startup-bound project catalog: `this.d.adapterCatalog` with `this.d.instances?.()` in Tasks, `opts.adapterCatalog` in `assign()` and `relayCandidates`, and the `adapterCatalog` hook in `Conductor`/`conductorStart`; a bare `definitionOf(id)` answers builtins only. Only a kind's routing preference expands to its instances; manual ids, reservations and nominations stay exact. Reviewer independence compares families: take the own family only when no other is eligible, record it through `assignmentNote` (`sameFamilyFallback=true`), explicit nominations included; persist the `instance`/`family` refusal codes as `policy`.
56
56
  - Tool publication (`projectToolList`, `projectConductorTools`, `configuredToolContext` in `src/hub/hub-tools.ts`) only narrows the list or adds start targets, using authenticated status metadata (`configuredAdapters`, `startablePeers`, `readOnlyPeers`); in a configured project, malformed or incomplete metadata publishes the read-only set and adds no start target. Publication is never authority: `taskOp` refuses read-only mutations independently, and MCP task and conductor refusals keep their text and set `isError: true`.
57
- - SDLC metrics and bands (#481 S7): `sdlc-metrics.ts` is pure (history in, numbers out, no clock, no I/O); every metric a task cannot derive is `null`/`unknown`, never 0, and a private task is only counted as withheld. Stale-head re-reviews need both heads on a round (`reviewCost.head` on the done, `reviewFindings.head` on the verdict): never derive them from event order, the board cannot produce an `approved` followed by a `done`. `Tasks.sdlcMetrics` is the one read (planner or person, refused during a private turn) and `Tasks.proposeFromBands` the one writer. It is held like every host writer (`sweepHeld`, `bus.isRecoveryHeld`, `bus.storageError`), keys a breach by its episode (`BandResult.episode`, derived from history so a restart finds the same key), writes nothing while the metric has an open band card, and records a log notice in `band_notices` so it is announced once. Card text is fixed words, numbers and task ids, screened through `signalsFor` and `candidateTextAllowed` like the recurring-finding card; never put task titles, notes or PR text in it, and never open a GitHub issue. Drift honours `min_delta` like every tier. `[bands]` is validated by `bandsConfig` with field-level paths; do not add a tier or a metric without a test that fires it. Merge-time figures (#442) come from `MergeRecord.cost`, written once by `guardedMerge` after it observes the merge (`mergeCostFrom`, `cleanMergeCost` in `cost-governance.ts`): re-validate a stored record with `cleanMergeCost` before use, never let that read touch the merge result, and keep a missing or partial read `null`, never 0. `hotfix_rate` and `security_open_alerts` are external series (`EXTERNAL_METRICS`, `Tasks.externalSeries`) whose points are releases and scans, so a card names its `subject`; a legacy release with no train history is left out of the series, never a 0. The security scan (`security-baseline.ts`, `Tasks.runSecurityScan`) reads GitHub's own open-alert counts, runs no scanner, schedules nothing without `[security_scan]`, is held like every host writer, re-judges its schedule after the awaited read and records counts only; a source it cannot read is an unknown total, never zero.
57
+ - SDLC metrics and bands (#481 S7): `sdlc-metrics.ts` is pure (history in, numbers out, no clock, no I/O); every metric a task cannot derive is `null`/`unknown`, never 0, and a private task is only counted as withheld. Stale-head re-reviews need both heads on a round (`reviewCost.head` on the done, `reviewFindings.head` on the verdict): never derive them from event order. `Tasks.sdlcMetrics` is the one read (planner or person, refused during a private turn) and `Tasks.proposeFromBands` the one writer. It is held like every host writer (`sweepHeld`, `bus.isRecoveryHeld`, `bus.storageError`), keys a breach by its episode (`BandResult.episode`, derived from history so a restart finds the same key), writes nothing while the metric has an open band card, and records a log notice in `band_notices` so it is announced once. Card text is fixed words, numbers and task ids, screened through `signalsFor` and `candidateTextAllowed` like the recurring-finding card; never put task titles, notes or PR text in it, and never open a GitHub issue. Drift honours `min_delta` like every tier. `[bands]` is validated by `bandsConfig` with field-level paths; do not add a tier or a metric without a test that fires it. Merge-time figures (#442) come from `MergeRecord.cost`, written once by `guardedMerge` after it observes the merge (`mergeCostFrom`, `cleanMergeCost` in `cost-governance.ts`): re-validate a stored record with `cleanMergeCost` before use, never let that read touch the merge result, and keep a missing or partial read `null`, never 0. `hotfix_rate` and `security_open_alerts` are external series (`EXTERNAL_METRICS`, `Tasks.externalSeries`) whose points are releases and scans, so a card names its `subject`; a legacy release with no train history is left out of the series, never a 0. The security scan (`security-baseline.ts`, `Tasks.runSecurityScan`) reads GitHub's own open-alert counts, runs no scanner, schedules nothing without `[security_scan]`, is held like every host writer, re-judges its schedule after the awaited read and records counts only; a source it cannot read is an unknown total, never zero.
58
58
  - Admit a declared extra (a person-started `ahub claude --as` instance and a headless Claude implementer included, #742, #747) as owner of any class whose owner role is implementer (`ownerRoleOfClass`) only with the current implementer role and `ownerIsolation[id] === true`, the daemon's proof that the live adapter generation started in `executionRoots[id]` outside the canonical cwd. Never derive it from the declaration's `worktree`, its roles or a reload. Only the status path passes a display `ownerIsolation` projection (`stalled(projection)`); every admission and effect caller uses fresh `TasksDeps.ownerIsolation`.
59
59
 
60
60
  - Lifecycle identity (#694): the hub links `sdlc.repo`/`issue` only through `Tasks.linkIssue` (the one issue `planIssueIdentity` resolves) at propose and accept (`linkOnWrite`, #723: a local parse, never a read, never a refusal, advise and enforce only), plan review, done and the groom backfill (`backfillIssueLinks`: first in `groomingDigest`, 32 links and 1 s per call, before the slow metadata reads), and stages a done's PR through `Tasks.identityLink`/`withLink` (the link carries the repository and the commit or branch it was read for, and is dropped when the done's effective repository or head changed during the read; the issue is re-resolved at apply time; `reviewRequestAdmission` compares the board copy through the same `stage` projection) (`openPrFor`: the one open PR whose head is the done's full commit, or with no commit its branch; a full 100-row page or a malformed row is unknown). Persist a done's link only in the admitted done update or `done (checking)`, never before a gate that can still throw `PreEffectToolRefusal` (#254); reapply it after every re-read in `complete()`. Write a first link with `linkedReceipt`, never `sdlcReceipt` (its relink rule would drop red-first and local-gate records); never relink an existing or foreign identity, never parse agent text for a PR, skip PII tasks before any read, and run the groom backfill after its authority checks with its own bound.
@@ -66,7 +66,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
66
66
 
67
67
  - `Tasks.review` captures `reviewAuthority` once and rechecks it in `valid()` after every await, right before each write; a headless reviewer's verdict, and its `hub_task_show` (checked before and after the read), holds only while the same child runs for that task at its bound envelope head and review revision (`Tasks.reviewBindingCurrent`, host-only: `in_review`, same reviewer, non-PII, matching `reviewRequestRevision` and review head). Never cache the answer (#223, #533).
68
68
  - Review scope (#852, `src/hub/review-scope.ts`): a reviewer reads `full` (code, affected tests, CI) or `code` (code only). The default is data on the fixed reviewer variants (`AdapterDefinition.reviewer.scope`, `code` for the sandboxed codex and kimi variants, fixed-builtin-only like `instances.modelSelect`); a fresh-reviewer instance may override it with `review_scope` from `config.local.json` only (tier 3, never from a tracked file or the dashboard). `TasksDeps.reviewScope(peer)` is the one read. `sendReview` replaces the Hub-verified, check-result and no-check lines with `CODE_SCOPE_LINE` for a `code` reviewer, which requires every code defect as a structured finding with an id. Reserve findings for defects in the reviewed code, docs or tests; missing check/CI/test-run/PR-or-branch identity evidence goes in `unmet`, never `findings` (Amendment 2). The verdict rule is structural (amended AC3): a `code` reviewer's `changes_requested` is always recorded, never refused, and never classified by reading its unmet text; never add a text classifier (background: README). If its structured `findings` carry no item with disposition `open`, the history entry is `evidenceOnly: true`: it adds nothing to `rejections` (so it cannot escalate), records no failed outcome, contradicts no earlier approval, writes no recurring-finding candidate, is not a rework round in the SDLC metrics, still returns the task to its owner as usual, and sends the configured conductor one ids-only task notice (`evidenceOnlyNotice`; with no conductor, one console notice). A verdict with an open finding counts as today. A person's and a `full` reviewer's verdict are never evidence-only. A `code` reviewer's verdict history entry carries `reviewScope: "code"` and a `full` reviewer's carries nothing, so old rows are unchanged. Merge rule: a task approved by a `code` reviewer is merged by the conductor or the person only after exact-head CI is green and no review thread is unresolved, and the merge note names the CI run.
69
- - Owner review re-file (#1000): admit a replacement `hub_task_done` while `in_review` only for a different full lowercase SHA through the existing owner, privacy, lifecycle and completion-check gates. Fence the original review revision through every await; same-head, active-check and superseded submissions refuse before effects. Clear stale artifact-head evidence without minting reviewer consent, retain current-head local evidence, open Important findings and rejection counts, and publish a fresh request. A configured check returns the task to `in_progress` until completion. Every verdict pins its initial review request revision across awaits, including off mode with no explicit head.
69
+ - Owner review re-file (#1000, #1047): admit a replacement `hub_task_done` while `in_review` or approved and unmerged only for a different full lowercase SHA through the existing owner, privacy, lifecycle and completion-check gates. Fence the original review revision through every await; same-head, active-check and superseded submissions refuse before effects. Clear stale artifact-head evidence without minting reviewer consent, retain current-head local evidence, open Important findings and rejection counts, and publish a fresh request. A configured check returns the task to `in_progress` until completion. Every verdict pins its initial review request revision across awaits, including off mode with no explicit head. On an approved unmerged task, allow only the reviewer/person to withdraw approval with `changes_requested`; retain Important findings and leave that state for the owner. Persist each verdict head in history and preserve a legacy receipt-only approval head before dropping stale evidence. Refuse merged/cancelled reopens and unknown linked-PR merge state; re-read merge state after other awaits before the reopening write. Require a reviewer after a re-file, including configured-check completion. Project approval binding through `task-approval.ts`; merge queue, decision, dispatch and cleanup must compare it with the latest reported head.
70
70
  - Keep structured review findings in verdict history, bound to the reviewed head. Validate bounded reports and person-only accepted dispositions before effects; recheck authority, privacy and head identity after awaits. A supplied `head` that differs from the task's resolved head (`findingHead`) is refused pre-effect, and the resolved head is part of the identity rechecked after the screen (#657). Key findings by `id` alone, never by pass (`findingsForHead`): a nit or open reclassification never discharges an Important, only an explicit disposition does. Omitted IDs never settle findings; carry open Important across heads until explicitly dispositioned, while nits stay head-local. Use the shared valid-head resolver for projections. Project text through publicView, and deduplicate recurring public pass/id pairs into backlog proposals through durable `Review finding key:` markers (a pair recurs only across separate work: tasks sharing a PR, a branch in one repository or a supersession count once, `workUnits`, #736; a task counts only where a review that is not withheld recorded the finding's id, whatever its pass, as open or accepted, never where it is only recorded fixed, #328) read only from hub-proposed `plan` cards (first history entry by `hub`), without editing instruction files.
71
71
  - Recurring unmet criteria (#514a, `src/hub/learning.ts`): grouping is pure (rows in, groups out); `Tasks.proposeCriterionCandidates` is the only caller and runs beside `proposeRecurringFindings`. It files a backlog card with ids and counts and the `profile_overrides` fragment as text, never applies it, and counts only rows that `candidateTextAllowed` passes (no PII). Its dedupe is the `Criterion candidate key:` line; keep it first in the detail. `ahub learn` (#514b) is the person's verdict on those cards: `parseCandidateCard` reads only that first line (and the hub-authored `plan` card, and the title that must match it), the task count and the line are recomputed from the board (`learnEvidence`, `overridesDiff`), and the card closes through `Tasks.cancel` only after `settingsSet` wrote the one managed key `LEARN_CHECKLIST` (never before, never by a direct board write); every `Tasks.learn*` method refuses any `by` but the person, and a new learn op never joins `CONDUCTOR_TASK_OPS`. Rewording the card's key line or title changes the parser in the same change.
72
72
  - Plan conformance (#481 S2, `src/hub/plan-conformance.ts`): compare only literal repository-relative `plan.paths` entries (file or directory prefix, no glob) with complete PR file metadata; `completePlanInventory` requires a full head, `pathsComplete` and `typesComplete` (false on any rename or copy). Root, absolute-unverified or unsafe entries, a missing plan, a truncated inventory and a failed read are `unknown`, never covered; never read patches or the filesystem. An untouched planned path is advice; a changed path outside the plan is a departure, and `planConformanceBlockers` blocks enforce approval on `unknown` and on an unaccepted departure. Plan `proof` and `risks` (32 strings of 512 characters) cover no file. `Board` records `planRevision` only while `trackPlanRevisions` (mode not off), separate from backlog `planningRevision`.
@@ -78,7 +78,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
78
78
  - `review.same_family = "never"` (#820) is read only inside `assign()` through `reviewPool.sameFamily`, which `Tasks.health()` and `reviewPoolOptions` pass from `d.review.same_family` (absent for the default `"fallback"`, so default routing stays byte-identical). It removes every same-family reviewer: the selector's last-resort loop (`selectReviewerPool` `sameFamily`), the legacy `automatic` fallback, a named same-family reviewer (refused with code `family`, `same family as <owner>; review.same_family is never`) and the planner escape unless the planner holds the reviewer role and is of another family. Nobody left means `reviewer = "user"` with `reviewBlocked`, never `sameFamilyFallback`, and that holds for any automatic exhaustion (all offline, no candidate, no declared instance), never `reviewer: none, done will approve directly`; the person fallback is keyed on the setting, not on an eligible same-family candidate. Keep it out of call sites: `Tasks` never tests the family itself.
79
79
  - `Tasks.retryReviewer` is host-only (the daemon's reviewer `onUnavailable`), never a tool or CLI path. It acts only while the failed peer is still the reviewer of an `in_review`, non-PII task, with the same review head, the request's `reviewRequestRevision` and a current child generation; `valid()` rechecks all of it after every await and right before the write, which also passes the flow revision. A manually named reviewer's `no_verdict` and a no-verdict with no declared pool go only to the person; other manually named failures keep explicit person control. Confirm fallback only after accepted reviewer publication or a person offer and a fresh binding/generation check; failed publication or notification never confirms settlement. Each failed peer is tried once per head and round (`reviewerFailure` history). `reviewRequestRevision` counts only role events (`done`, `reviewer changed`, `reviewer unavailable`, ownership), so bookkeeping never fences a request; keep `refs.reviewRevision` and `refs.commit` on the review envelope.
80
80
  - Release trains (#481 S11): change trains only through `Tasks.newRelease`, `configureRelease`, `assignRelease` and `cutRelease` (planner or person), and refuse every `hub_release_*` in a PII turn. `assignRelease` writes only `release` and refuses a frozen, shipped or dropped release on either side; private or pinned scope is person-only. One active or frozen train at a time. A cut freezes only through `Board.freezeRelease` after a fresh `releaseWindow` (host lease from `readReleaseHostAdmission`, quota, `current()`, at most 5 s old); unknown evidence defers, and the window is closed in `finally`. A cut never publishes, tags or upgrades, and upgrade and recovery never consult `releaseWindow`. Keep two of `RELEASE_TRAIN_HISTORY_LIMIT` slots for cut and ship; a configuration no-op appends nothing. An S9 plan receipt survives only contiguous planning revisions that changed exactly `["release"]`.
81
- - Release authorization (#481 S6, `src/hub/sdlc-release-authorization.ts`): only the person's console `sdlc authorize-release` creates one, bound to the current HEAD commit, its committed package version, the origin identity and a fresh, error-free docs verdict, all reread after the docs check. It lives in daemon memory only: a restart revokes it and the event is audit, never restored. `release-guard` and `release-guard-hook` are refused in a private turn; a tag push needs a matching receipt, and npm publish also a clean tracked worktree and the same docs digest. Never accept authorization from a tool, a task receipt or a file.
81
+ - Release authorization (#481 S6, `src/hub/sdlc-release-authorization.ts`): the console user or the one configured conductor through `sdlc authorize-release` creates one under the same admission as `sdlc merge` (tools role, matching configured conductor, no `toolsOwner`, private turn or caller PII context). Keep the actual asker in receipt `by` and the audit, and the cooperative explicit-person-instruction rule in AGENTS.md Releasing. Keep consent bound to the current HEAD commit, its committed package version, the origin identity and a fresh, error-free docs verdict, all reread after the docs check. It lives in daemon memory only: a restart revokes it and the event is audit, never restored. `release-guard` and `release-guard-hook` are refused in a private turn; a tag push needs a matching receipt, and npm publish also a clean tracked worktree and the same docs digest. Never accept authorization from a tool, a task receipt or a file.
82
82
  - `Board.update`'s untouched-return exception is the only way owned or accepted work returns to backlog: keep every condition (exact event, owner and reviewer null, state, `expectedFlowRevision`, non-empty note, no work evidence) and reach it only through `Tasks.returnUntouchedToBacklog` (#481 S9).
83
83
  - The main-checkout note (`mainCheckoutWarning`, `src/hub/main-checkout.ts`, #572) is advisory text in the `hub_task_done` result, the review request and status, never a refusal. A branch name or other metadata the hub reads from outside the board reaches task text only after `Tasks.publicMetadataText` (PII patterns, secret shapes, private paths) and a plain ref-name check; otherwise use the generic sentence that carries none of it.
84
84
 
@@ -110,7 +110,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
110
110
  - Schedules (#512a, `src/hub/schedules.ts`, `Tasks.proposeFromSchedules`): the hub proposes a scheduled task only through `Tasks.propose` as `HUB`, never `Board.propose`, so the PII screen, class validation, triage and routing apply. "Its previous task is open" is read from the board (`openScheduled`: first history entry by the hub, detail starting `Schedule key: <id>`, state not closed), never from `schedule_runs`. A run restarts the period at `now`; a hold or an open task counts a skip and proposes nothing, so a long hold ends in one task. Status rows carry ids, times and a state, never the title or detail. A schedule is `every` or `cron` (+ `tz`), never both (#1017): every consumer of its period goes through `scheduleNext(entry, anchor)` and `scheduleZone`, never `parseEvery(entry.every)` (`every` is absent on a cron entry), and a view carries `cron` and `zone` instead of `every`. The cron core is pure and takes its time as an argument; keep the clock and the machine zone out of it, and keep the daylight-saving rule (a skipped time runs at the next valid minute, a repeated one once) in `instantOf` alone. No third-party cron or time-zone library.
111
111
  - Research campaign dispatch (#930, #597; `docs/specs/2026-11-01-research-campaigns-design.md` "T2 dispatch and coverage implementation boundary"): a task bound to a campaign unit reaches an owner only through the paths that call `studyDispatchReason` / `studyDispatchCheck` (offer, assign, planner pull, owner change, acceptance, and a queued owner offer re-checked at delivery). Each reads the stored campaign, its deadline and stop limits, and for a hard cap the named budget, synchronously, and `assignOwner` asks again immediately before the owner write with nothing awaited between that read and the write; a new owner write asks the same read there rather than checking earlier. An explicit request refuses before any effect; the hub's own offer holds with `holdReady`. Budget enforcement is the named budget by id (`ExecutionBudget.covers`), never `applies` (any budget), and it only reads: a campaign never resets, copies or writes a counter, and a `kind: "run"` budget stays peer-wide. A read that fails holds, never dispatches, and a hold never touches a native turn already running. `studyShow` returns ids, enums, counts and times, and strips source ids from a private campaign. A later slice that exposes a new dispatch or wire requirement adds its own `DAEMON_FEATURES` label and recovery guard.
112
112
  - Campaign storage (#930 T1, #596; spec "T1 implementation boundary"): every study write runs inside `studyWrite` (counted by `studiesPending`, which `recoveryReady` and the shutdown drain read; `stopStudies` fences new ones) and asks `requestCheck(by, () => this.studyHold())` again after every await and right before the board write. `studyApprove` screens and builds units outside any transaction, re-reads the latest hash and references, then writes once through `Board.activateCampaign`; never move a screen, source lookup or file read into that transaction. A replay creates nothing (it may only tighten privacy), a stable unit id keeps its task only for an identical definition (changed work takes a new id), and a private campaign never retains a public task. `research-campaign.ts` validates only inert plain data (plain prototype, no proxy, accessor or symbol key; a caller's `toJSON` never runs) before canonical hashing. A receipt carries ids, revision, status and counts; no raw contract or close reason enters parent history or memory.
113
- - Experiments (#932, #598, #599; spec "Experiment T1 implementation boundary" and "T2 comparison implementation boundary"): `Tasks.experimentRecord` and `experimentShow` have no hub tool or CLI yet; `study_approve_protocol` (`experimentApproveProtocol`, person only) is the one wired op, and a new tool or form needs its own admission and recovery review. A writer is the current in-progress owner of a unit task in the protocol's current approved campaign revision (`Board.researchDispatchBinding`), rechecked after every screen, receipt read and hash. A host measurement is its own `research_attempt_observations` row, never a rewrite of a declared event, and no path upgrades a provenance level. Output-path privacy uses `matchesPolicy` with `failClosedOnDiscoveryLimit` (`experimentOutputPrivate`); keep that opt-in to experiments, never the default for other callers. Approval binds the exact protocol hash and evaluator hash, never carries to a changed hash, and a comparison never ranks.
113
+ - Experiments (#932, #598, #599; spec "Experiment T1 implementation boundary" and "T2 comparison implementation boundary"): `hub_experiment_record` and `hub_experiment_show` are thin callers of `Tasks.experimentRecord` and `experimentShow`; the person alone approves through `study_approve_protocol`. Forward the complete record envelope and authenticated actor unchanged, keep record calls out of read-only publication, refuse all PII-turn contexts, and exclude their arguments/results from capture. Only the existing receipt kind admits a person relay; ordinary records keep the current in-progress unit-owner check. New forms need their own admission and recovery review. A writer is the current in-progress owner of a unit task in the protocol's current approved campaign revision (`Board.researchDispatchBinding`), rechecked after every screen, receipt read and hash. A host measurement is its own `research_attempt_observations` row, never a rewrite of a declared event, and no path upgrades a provenance level. Output-path privacy uses `matchesPolicy` with `failClosedOnDiscoveryLimit` (`experimentOutputPrivate`); keep that opt-in to experiments, never the default for other callers. Approval binds the exact protocol hash and evaluator hash, never carries to a changed hash, and a comparison never ranks.
114
+ - Registered runner receipts (#1018): admit a new signed receipt only through `Tasks.experimentRecord`, with current task/source/campaign/privacy authority and fresh trusted registrations rechecked after screening. Keep declared, host-observed and runner-attested evidence separate; a deliverable protocol never derives a code match from a signed commit claim. Identical canonical receipt/signature replay returns stored proof without registration verification or writes, even after revocation; changed bytes or a new revoked/unknown-key receipt refuse. Validate the whole linked attestation event and absence of host observations on every stored receipt read, including paged views that omit that event. Store receipt/event and advance format 2 in the same transaction; keep new/unused databases at `HUB_DB_BASE_SCHEMA` (1). Preserve task, privacy and source fences and show current key status separately from as-of-signing provenance. No public recording surface runs or contacts a runner; keep relay arguments/results out of memory capture.
114
115
  - Study readback (#930 T3, #601): `ahub study draft|revise|approve|close` and the hub tools are thin callers of `Tasks.study*` and add no board write. The daemon's person ops are `study_draft|revise|approve|approve_protocol|close` (`taskOpBody`), refused on any non-console connection by an explicit `study_` check in the `task` control case before `Tasks` is asked; agents use `hub_study_draft` and `hub_study_revise` (configured planner through `planningAuthority`, never approve), `hub_study_show` (read-only, also in `READ_ONLY_HUB_TOOLS`) and `hub_study_handoff` (the bound in-progress owner). Pass the tool's arguments to `Tasks` whole: `studyFields` refuses an unknown field, and re-picking fields in the daemon would silently drop it (mutation-checked in `test/study-daemon.test.ts`). `STUDY_TOOLS` keeps the four tools' arguments and results out of memory capture. An implementer child gets `hub_study_show` and `hub_study_handoff` only. `Tasks.studyList` returns one page `{ campaigns, next? }` of receipts (ids, revision, status, counts), never question, scope or sources, at most 50 per call: the bound is the `LIMIT` in `Board.researchCampaignPage` (observed on its own SQL in `test/research-campaign-projection.test.ts`), `after` is the cursor, and a caller cannot raise the page size. `studyShow` blanks source ids for a private campaign and carries `budgetNamed`, so a named budget keeps every resource row with `-` for an absent limit: do not add text to either. `liveTestRefusal` refuses every `study` form except `show`, so an agent shell cannot delegate a study write to the person through `--live-test`; the contract file is read through one opened regular-file descriptor, at most 64 KiB plus one byte.
115
116
  - Folder triggers (#512c, `src/hub/folder-trigger.ts`, `Tasks.runFolderTriggers`): the folder pass shares `schedulesRunning` with the schedules pass and `ahub schedule run`, so it is serialized with them and counted by `schedulesPending`; keep it so, and never start it from a path that does not take the guard. A file is recorded in `folder_seen` only AFTER its task exists, and the pass stops at the first failure so order is kept; do not record first (a failure would lose the file) or continue past a failure. A task made for a file names the source id and never the file's name or path, because both can be private. A trigger's baseline (the files there when it starts, possibly over several polls) proposes nothing, and a refused folder never begins the trigger; do not make either propose. The scan must keep no file's bytes (it hashes in chunks under a file and byte budget and never cuts the listing); `registerScanned` re-reads the one file being registered and refuses it unless it still hashes to what the scan saw. Ask `isNew` for each file inside the loop, not only when building the list, and `observe` (never skip) every file whose content is already known, in the list and in the loop, so it is found by `known` next time and cannot starve a new file of the hash budget. `folder_triggers` stays in `MACHINE_LOCAL` and `NEVER_EDITABLE`.
116
117
  - A scheduled run is fenced twice (#512a): `proposeFromSchedules` checks the hold before it starts and passes `propose` a `fence` that `requestCheck` asks again right before the board write, because the PII screen and triage are awaited in between and `requestAuthority` only fences child generations. Any other host writer that awaits a screen before `Board.propose` needs the same fresh admission at the write. A recovery commit also waits for a pass in flight (`Tasks.schedulesPending`, read by `recoveryReady()` in the daemon): every host writer that can write across an await gets a pending count there, next to `checksPending` and `autoPullsPending`. A person's schedule command that changes what a run in flight should do (`pause`, `resume`, #335) must reach that run's fence too: `ScheduleStore.epoch` is bumped by `setPaused` and `runSchedule`'s fence compares it, so the acknowledged command wins over an admitted run; `ScheduleStore.ran` never moves the anchor back. Any manual entry into the same writer (`Tasks.runScheduleNow`) takes the same `schedulesRunning` guard and is registered with the shutdown drain (`manualSchedulePasses` in `startDaemon`), never called around it.
@@ -11,6 +11,7 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
11
11
  - Sandboxed commands and the MLX server get a fixed environment (`PATH`, `HOME` and their own keys), so test knobs for their fakes travel in a wrapper script or argv, not in `process.env`; native peer children inherit the hub's environment minus the agent markers (`peerChildEnv`).
12
12
  - Bind agent identity markers explicitly in native/CLI fixture children. The full gate starts the test runner without inherited agent markers (including `AGENTHUB_DELIVERY_ONLY`) and without an `ahub claude` session's hub variables (`AGENTHUB_PROJECT_DIR`, `AGENTHUB_STATE_DIR`, `AGENTHUB_INSTANCE_ID`, `AGENTHUB_LAUNCH_ID`); a new variable a native session exports and a fixture child reads joins that `env -u` list in `scripts/check.sh`. Never weaken production marker detection to make them pass.
13
13
  - A fixture that spawns the real channel plugin (`src/adapters/claude-channel.ts` or `plugins/agent-hub/server.js`) to act as a peer runs it with `cwd` at the hub's project root, made with `realPath` (the macOS temp dir is a symlink). The plugin chooses its hub through `channelProject` (rule: `daemon.md`), so from another cwd it joins the wrong hub, joins as a distinct session or connects nowhere. Only tools mode with both `AGENTHUB_STATE_DIR` and `AGENTHUB_PROJECT_DIR` set skips that check.
14
+ - Orca environment (#613, #1048): the `test/hub-home.ts` preload also deletes every inherited `ORCA_*` variable, and `scripts/check.sh` unsets them before `bun test`. A run from an Orca pane otherwise reaches the person's Orca through `ORCA_WORKTREE_ID`, `ORCA_TERMINAL_HANDLE` and `ORCA_CLI_COMMAND` (the launcher's four names are `ORCA_LAUNCHER_ENV` in `src/hub/orca-env.ts`, the one list; `personEnv` drops the whole prefix) and opens real tabs and agents there. A test that needs one sets it itself after the preload and restores it; a test of the launcher uses a stub `ORCA_CLI_COMMAND` and never the real one. `test/orca-env-guard.test.ts` runs a real `ahub up` with fake values and a recording stub and must see zero calls.
14
15
  - `bunfig.toml` preloads `test/hub-home.ts`, which first clears inherited `AMEM_HOME`, gives every test run (the gate and `bun test <file>` alike) a temp `AGENTHUB_HOME` unless one is set, and always replaces `KIMI_CODE_HOME` with a temp dir, an inherited value included (#285); scoped tests may set their own `AMEM_HOME` or `KIMI_CODE_HOME` after preload. Hub memory (#258) is on with any project config, and no test may write the person's `~/.agenthub`. Do not remove it; a test that needs its own home sets and restores the variable. The preload keeps an inherited `AGENTHUB_HOME`, which an agent shell exports as the person's hub home and `scripts/check.sh` does not unset: run `bun test` and the gate with `env -u AGENTHUB_HOME`.
15
16
  - `test/registry-guard.test.ts` statically scans `src/` against the committed `test/registry-guard.baseline.json` (#451); a test never writes, regenerates or repairs that baseline, and a failing guard is fixed in the source by moving the policy into a definition (rule: `docs/agent-notes/adapters.md`); the baseline stays `[]`. Scanner behavior is pinned on inline sources in `test/registry-guard-fixtures.test.ts`, never on edits to real files.
16
17
  - A fixture that needs a port takes it from a free-port probe, never a fixed number, which collides with whatever holds it on a shared runner (#548). The probe binds `127.0.0.1`, matching the loopback server that takes the port (#232).
package/docs/dogfood.md CHANGED
@@ -12,6 +12,9 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
12
12
  | --- | --- | --- |
13
13
  | Distinguishable sessions and sender provenance (#1036, `session_identity`) | on after release and upgrade: a connection that names a peer id but is not that peer's current session is a distinct `<peer>~<origin>` session that can send and read, holds none of the peer's roles and replaces nobody; the channel plugin binds to its own project; every envelope and attach event records the connection. Nothing is refused to a connection that worked before. The recovery label `session_identity` applies once an unsettled delivery holds a session's message | `grep '"session":true' .agenthub/state/events.jsonl` lists every message a foreign session sent, with its minted id and no path or text; a recipient's header shows `from "claude~<id>"` for it; test/identity-provenance.test.ts (AC1, AC3, AC4, recovery label) and test/identity-channel-project.test.ts (AC2, real plugin against a new and an old hub) hold it. Not authentication: a same-user process can always act as the user |
14
14
  | Cross-project task handoff with state follow-back (#604, `task_handoff`) | on after release and upgrade: `ahub task handoff` and `hub_task_handoff` register work for another registered project that runs agent-hub on this machine as an ownerless proposal on its board; the requesting peer is told each state change as a `kind: task` message. Nothing is queued, and the origin has no authority there | `ahub board --handoffs` lists what this project handed over with the last state each target reported; `ahub board` and `ahub task show` in the target name the origin project and peer; test/handoff-daemon.test.ts runs two real daemons (AC1 to AC5 and the recovery label), test/handoff-core.test.ts and test/handoff-board.test.ts hold the pure core and the tables |
15
+ | Staged project upgrades (#1039) | off: available for the person to upgrade projects independently; this development hub is not upgraded by a fixture | test/published-protocol-compat.test.ts uses the published 0.25.0 client/plugin and reverse daemon; test/staged-upgrade.test.ts checks independent holds and scoped effects; test/staged-runtime-daemon.test.ts checks a real release-root daemon after mutable install removal; test/board-schema-guard.test.ts checks named pre-effect refusal and WAL metadata; `test/project-runtime.test.ts` and `test/project-runtime-lifecycle.test.ts` cover updated resolver retention/refusal and real lifecycle/dashboard routing with synthetic captured daemons; `test/runtime-capture-prune.test.ts` covers references, bounded retention and publication races; `test/upgrade-stopped-project.test.ts` verifies no preservation for stopped projects. These fixtures do not prove published pre-0.26 CLI behavior |
16
+ | Approval head binding and re-review (#1047) | off until release and project upgrade; fixture evidence only, this PR does not change the running hub | `test/task-review-refile.test.ts` covers owner re-submit, reviewer withdrawal, legacy verdict heads, merged/cancelled refusals and held-screen races; `test/sdlc-queue.test.ts` and `test/sdlc-merge.test.ts` reject stale approval at queue and dispatch. `ahub task show <id> --json` exposes `approval.head`, `latestHead`, `mismatch` and `current`; verdict history retains `reviewedHead` |
17
+ | Default agent set started by `ahub up` (#608, `start.agents`) | off here: this repository's agents are started by their own terminals; it is a machine-local setting and a committed config cannot turn it on. On for a person who runs `ahub setup` and `ahub up` on a project that lists agents. | each `ahub up` prints one line per agent (started, already attached, or the refusal with the manual command); the `peer_start` replies in `hub.log` and the terminal provider's recorded starts show whether a listed agent attached, and `ahub status` shows its state after |
15
18
  | `ahub down` (counterpart of `ahub up`) | on: an alias of `ahub kill` for stopping this project's hub | the person stops the hub with `ahub down` and `ahub status` then reports it stopped; test/identity.test.ts keeps it person-only (`console`) and labelled `down`, and test/help.test.ts holds its help entry |
16
19
  | Encrypted rooms (#446 X1) | off; source-only crypto/wire, with no broker, room link, invitation CLI or delivery enabled | real WebCrypto vectors and negative wire fixtures; atomic memory fake ports establish ordering only; durable X3 state and the X7 two-machine person receipt remain unverified |
17
20
  | Blind room broker (#446 X2) | off; standalone source/fixture work, no public TLS service or hub room link configured here | fake-client auth/fan-out/revocation/restart, ciphertext-only child-process canary, owner admission/offline room reset, queue caps/TTL/FIFO/migration, safe directory modes, and person-only CLI tests; live proxy/account/X7 receipts remain unverified |
@@ -82,7 +85,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
82
85
  | Merge-time PR readback, release metrics and security-scan baseline (#481 S7, AC12) | source and fixture coverage with a fake gh and real train transitions; the readback is on wherever auto merge is `on`, release metrics need shipped trains with recorded history, and the security scan and its bands stay off here until `[security_scan]` and `[bands.security_open_alerts]` are set | `cost` on merge records, `ahub sdlc metrics` review/CI-at-merge rows, the releases row of `ahub sdlc release`, `security_scans` rows and `security_open_alerts` cards |
83
86
  | Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
84
87
  | Seed anchor diagnostics (#684) | on in the seeded checker; exact-match failure remains mandatory | missing anchors report the base commit, enclosing symbol and current file/line or explicit unknown; `test/seeded-check.test.ts` |
85
- | Release authorization (#481 S6) | pending (#199) | releases with a receipt |
88
+ | Release authorization (#481 S6, #1063) | fixture coverage; delegated live consent awaits release and project upgrade | `test/sdlc-release-authorization.test.ts` and flow fixtures retain exact candidate/docs/origin/restart bindings and record the actual user/conductor actor; agent-read and floor fixtures refuse other peers, private turns and live-test; no fixture authorizes a live release |
86
89
  | SDLC metrics and control bands (#481 S7) | `ahub sdlc metrics` on; bands off until `[bands]` is set in `.agenthub/sdlc.toml`, and they write cards only in advise or enforce (in off the report still shows tiers). Completion checks are off here, so `first_pass_check` and the PR-based figures read unknown | its own report; breach episodes per week by tier, and band cards the planner accepted rather than dropped |
87
90
  | Fresh headless reviewers (#223, #519) | configured here (`codex-review`); per-tool approval correction and no-verdict diagnostics/re-offer (#783) take effect after release and upgrade | reviews by fresh sessions, findings they add; a per-review start failure names a closed cause class (#722): `ahub status` reviewer line and hub.log `[cause: ...]`, with the share of failures that are `unknown`; stale admission skips and current-policy `no_verdict` reroutes leave the reviewer available (#781); a real `hub_review` verdict per verified backend/version, beyond inventory-only proof; bounded refusal diagnostics, confirmed re-offers without queue holds, and uncertain fallback holds |
88
91
  | Completion checks per class | off: the implementer already runs `scripts/check.sh`; a hub-run copy would double the heavy-slot use | time to verdict when on |
@@ -146,7 +149,10 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
146
149
  | Study CLI, tools and readback (#930 T3, #601) | Fake peers and a real daemon over its control socket, plus the real `ahub` process against that daemon; no native peer or live model has run a campaign here, so fake versus native evidence stays separate (the `research-live-example` claim in docs/profiles.md is still open) | `test/study-daemon.test.ts` (person's draft, approve and close; no agent reaches an approve or close op; planner drafts and cannot approve; read-only show; a private source shows no source id, question, scope or path; handoff only from the bound owner with every missing field named; the three-source, two-peer example moving from none to incomplete to complete), `test/output-cli.test.ts` (parse before connecting, `--json` byte for byte), `test/study-guidance.test.ts` (guidance names only published tools) |
147
150
  | Research dispatch and coverage (#930 T2) | Source/isolated fixtures only; no live research campaign | `test/research-dispatch.test.ts` and projection/recovery fixtures: no expired/revised dispatch, named-budget coverage, handoffs and shared ledger |
148
151
  | Experiment records (#932 T1) | Source and isolated fixtures only; no live research attempt | Schema, storage, Tasks and recovery fixtures: declared/host provenance stays distinct, all attempts counted, replay and privacy fences |
152
+ | Experiment record relay (task 615, #932) | Fixture only; available after the next release and upgrade, with no live experiment or runner started here | `test/research-experiment-record-daemon.test.ts` exercises authenticated owner/person relay, role and PII refusals, idempotent replay and inert payloads; capture/publication fixtures exclude raw receipt/signature data |
149
153
  | Experiment comparisons and protocol approval (#932 T2) | Fixture only; no scientific reproduction claimed on this repository | `test/research-comparison.test.ts`, `test/research-comparison-tasks.test.ts`, `test/research-protocol-cli.test.ts` and recovery tests verify incompatibility, fresh exact-hash approval, causal post-hoc context and restart identity; failed/cancelled attempts stay counted. |
154
+ | Experiment readback and receipt fixture (#932 T3, #602) | Fixture only: the readback (`ahub study experiments`, `hub_experiment_show`) is on after release and upgrade, but no live research attempt has been recorded here, and no native model or account has used it | `test/research-experiments-fixture.test.ts` (baseline, variant, failed and changed-protocol cases, a private input, a missing and a denied output, retry versus repeat; every value's source shown, every status counted, nothing run) and `test/study-guidance.test.ts` (who may call the read tool); the real use signal is `ahub study experiments` output on a campaign that has recorded attempts |
155
+ | Registered runner receipts (#1018) | source and test-key fixtures only; no external runner or live scientific attempt has been admitted here | `test/research-runner-tasks.test.ts` covers signed binding, immutable replay, revocation/readback, privacy and schema-2 rollback; parser/projection fixtures cover missing fields and sealing negatives; config/recovery fixtures cover person-only trust roots, raw-key floors and integrity. `evidence.runnerAttested` stays separate from declared/host-measured values and reports current key status |
150
156
  | Dirty-submodule notices (#798) | source/fixture coverage; the live writing/general pilot (a workspace with 26 submodules) remains unverified | `ahub turns` shows `<path> (changed, contents unobserved)` for a turn that only edited a submodule; the D1 notice at `hub_task_propose` and `hub_task_accept`; `ahub undo` names what it cannot restore; a project without `.gitmodules` makes the same git calls per turn (counting-shim test) |
151
157
  | Profile scorecard (#799) | source/fixture coverage; live writing/general/research pilot remains unverified | ahub report --dogfood profile rows; pure and reader dev byte goldens, private-text exclusion, research declarations/unknown verdicts and per-source unknown tests; anonymous weekly receipt templates |
152
158
  | Domain packs (#318/#322, #513) | off here: this repository has no pack in `.agenthub/roles`, so `ahub init --profile <pack>` is not used on it; live adoption in another project is unverified | the `ahub init --profile <pack>` preview and apply, the pack validator and the shipped-pack catalogue, covered by test/packs.test.ts (40 mutants killed); adoption would show as a `.pack.json` under `.agenthub/roles` and a `profile` in `.agenthub/config.json` of the adopting project, read from `ahub init --profile` runs. The writing-pack, analysis and ops packs load with shipped roles; test/domain-packs.test.ts executes all three standalone checks on synthetic passing/refusing fixtures. Their notes remain advisory and no check is auto-selected; live domain-workflow use remains unverified |