@staix/agent-hub 0.19.0 → 0.20.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.
- package/CHANGELOG.md +11 -0
- package/README.md +2 -2
- package/docs/agent-notes/adapters.md +3 -2
- package/docs/agent-notes/bus.md +1 -1
- package/docs/agent-notes/daemon.md +6 -2
- package/docs/agent-notes/local-worker.md +1 -0
- package/docs/agent-notes/tasks.md +4 -2
- package/docs/agent-notes/tests.md +4 -3
- package/docs/dogfood.md +4 -1
- package/docs/memory.md +1 -1
- package/docs/operations.md +40 -8
- package/docs/quickstart.md +7 -3
- package/docs/sdlc.md +12 -0
- package/docs/security.md +4 -4
- package/docs/specs/2026-09-19-agent-hub-design.md +85 -3
- package/docs/verification/2026-10-27-0.20.0.md +75 -0
- package/docs/verified.json +24 -16
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +103 -7
- package/src/adapters/acp.ts +8 -0
- package/src/adapters/channel-peer.ts +7 -5
- package/src/adapters/claude-implementer.ts +1 -0
- package/src/adapters/codex-appserver.ts +11 -1
- package/src/adapters/conformance/enrollment.ts +4 -4
- package/src/adapters/conformance/factories.ts +2 -1
- package/src/adapters/conformance/kit.ts +10 -0
- package/src/adapters/local-worker.ts +2 -0
- package/src/adapters/pi.ts +7 -0
- package/src/adapters/reviewer-print.ts +1 -0
- package/src/hub/ci-watch.ts +140 -0
- package/src/hub/claude-launch-proof.ts +67 -2
- package/src/hub/config-apply.ts +2 -2
- package/src/hub/config-trust.ts +1 -0
- package/src/hub/daemon.ts +86 -79
- package/src/hub/peers.ts +34 -0
- package/src/hub/settings.ts +1 -0
- package/src/hub/task-shell-default.ts +3 -2
- package/src/hub/tasks.ts +139 -7
- package/src/local/deny.ts +21 -0
- package/src/local/sandbox.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ 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.20.0
|
|
8
|
+
|
|
9
|
+
- Pin @staix/agent-mem 0.11.0 (ranked search explanations, dated eval sets, and import and consolidation boundaries: Claude Code project-directory encoding, accepted-only relation targets, consolidation review receipts that survive pruning). Every amem that shares a memory home must run 0.11.0 or later to keep those receipts, so upgrade a global `amem` CLI too. Refs agent-mem#13.
|
|
10
|
+
- The daemon dispatches delivery, lifecycle, session, recovery, the Claude launch proof and the Codex proxy and revert through generic peer ports instead of native type checks (#625 slices 2b to 2e: #882, #887, #890, #891). Behavior is unchanged; the native-branch ratchet shrank from 64 to 30 rows, and one Codex `actionEnforcement` branch stays on purpose for a later action-policy port.
|
|
11
|
+
- Release preparation as an agent task and a guarded one-shot tag (#787 P3b): `scripts/release-prepare.ts` applies the version rule (any `feat` bumps the minor), README Status, CHANGELOG, bundle and a verification report skeleton on a `release/<version>` branch, lists native acceptance receipts from the repository and restamps only the documents an agent names with `--reviewed`; `scripts/release-tag.ts <version>` repeats the whole preflight, reads the person's consent through `ahub sdlc release-guard` and, only with `--execute`, makes the single push of the preflighted commit. Consent stays person-only and nothing here grants it. Refs #787.
|
|
12
|
+
- Opt-in `tasks.auto_pull` (#785 P1c, default off, machine-local, live): when a write frees a peer's planner slot, the hub pulls the top backlog card explicitly reserved for that peer through the existing planner pull, as the hub for the card's planner of record (history `by: hub`, the planner and an `auto_pull:` reason). Revision, dependency, reservation, pinned, frozen-release and WIP checks run unchanged; unreserved, other-peer and PII cards are never pulled, a committed config cannot enable it, and a refusal is a console notice. Refs #785.
|
|
13
|
+
- One bounded CI watcher per exact PR head (#785 P1b, `src/hub/ci-watch.ts`): when `hub_task_done` leaves a review whose exact-head CI is not green, the hub reads the PR once a minute until the CI result or a 45 minute deadline, cancels on a newer head, a verdict, a cancellation or shutdown, and tells the owner and the reviewer once with a `ci_completed:` task notice (ids only). A review held as premature for that head is re-offered once after green. No peer turn waits for CI and no gh read happens outside a watcher. Refs #785.
|
|
14
|
+
- The local-worker and Pi sandbox lets a command add only new loose objects to the shared git object store (#864): `objects/info` (alternates, commit-graph) and `objects/pack` are refused, and an existing object cannot be changed, removed or replaced, so a sandboxed peer cannot poison what every checkout and the Claude implementer's host add, commit and push trust. `git gc`, `repack`, `fetch` and `maintenance` now run outside the sandbox.
|
|
15
|
+
- `scripts/check.sh` counts test progress under mawk too (#883): the output filter reads line by line there, so the quiet watchdog no longer stops a healthy run on Debian or Ubuntu hosts without gawk
|
|
16
|
+
- A task's host-observed branch binding (#845) no longer counts as a move (#880): a binding that lands while the class check runs no longer turns the check result into "check finished late"
|
|
17
|
+
|
|
7
18
|
## 0.19.0
|
|
8
19
|
|
|
9
20
|
- The headless Claude implementer runs proven commands without asking in `ask-when-needed` (#845, #857): read-only commands and closed task-gate commands (check, test and build; `git add` and `git commit`; `git push` of the bound task branch; `gh pr` reads and create) go through a fixed host wrapper with a single-use ticket bound to task, child, root, branch, argv and environment, rechecked before spawn. Check, test and build code runs under the local-worker sandbox and cannot write project Git metadata (objects included); host add and commit run outside it with hooks, fsmonitor, signing and maintenance off. The default branch comes from the remote once at startup. `git add` and `git commit` still ask when Git filters or textconv drivers are configured; push and `gh` grants are kept with a global Git LFS install.
|
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.
|
|
8
|
+
Status: 0.20.0, control protocol 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
|
|
@@ -94,7 +94,7 @@ uses the home must run a version that understands the new layout.
|
|
|
94
94
|
[#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
|
|
95
95
|
|
|
96
96
|
Live checks that still need a real account or a person are listed in the
|
|
97
|
-
[release verification report](docs/verification/2026-10-27-0.
|
|
97
|
+
[release verification report](docs/verification/2026-10-27-0.20.0.md) and the
|
|
98
98
|
[smoke checklist](docs/smoke.md).
|
|
99
99
|
|
|
100
100
|
## Documentation
|
|
@@ -10,14 +10,14 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
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`; `BUILTIN_DEFINITIONS_WITHOUT_QWEN` is the frozen A1 export, read by no runtime consumer). 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
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`).
|
|
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
|
-
- Native proof state (
|
|
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`.
|
|
15
15
|
- When an adapter lands, add its project config directories to `AGENT_CONFIG_SEGMENTS` (`src/hub/permission-mode.ts`); scoped automatic writes must ask there, while explicit approval and `never-ask` keep their existing behavior.
|
|
16
16
|
- Load external modules only at daemon startup from trusted untracked local declarations: verify the self-contained bundle and content pin before importing captured bytes. Keep CLI/reload paths metadata-only, modules restart-bound and never settings-editable, builtin capabilities restricted, and external-kind instances refused in A4 (follow-up #287). Export conformance registrations and fake assets without starting them on import.
|
|
17
17
|
- Bind scanned external runtime SDK imports to this daemon's own `/adapter` entry; never resolve them against project packages. Refuse `/conformance`, `bun:ffi`, `bun:test` and CommonJS loading at runtime. Isolate module refusals from hub startup and retain unknown module roster rows as inert when metadata is unavailable.
|
|
18
18
|
- Require the immutable captured project catalog at instance, roster, execution and permission resolver boundaries; choose a builtin catalog explicitly only for builtin-only callers; accept `adapters` only as wholly machine-local trusted data and require restart for changes. Refuse missing/mismatched pins, non-echoed load sessions, leader/attach argv and unknown owned-group evidence; require an explicit standalone `--no-leader` before argv termination for Grok (kind, executable or pinned native name); signal only the original verified group. Keep unmapped policy unmanaged, absent budgets exempt and read-only instances non-raisable through native acknowledgement plus host/MCP mutation gates. Claim native support only with its isolated T0 receipt.
|
|
19
19
|
- Consume quota/context through the resolved definition's declared readers; absence stays unknown. Use explicit `attach.externalHello` and `instances` policy for admission, never `identity.mcp`; keep runtime owner/root and reviewer isolation proof in the host. Never let configured `roles` include `conductor`. Keep native stderr, prompt-failure, permission-mode and transport error text out of configured-kind logs and turn-failure reasons. A configured CLI start (`src/cli/configured-acp-entry.ts`) accepts only `--mode headless`, `--as`, `--session-id` and one preview flag, and its preview never shows argv or session values.
|
|
20
|
-
- Validate coherence and conformance enrollment for reviewer and headless variants too. Construct driven conformance peers through `start.create`; name dedicated coverage and concrete reasons for unsupported legs. Every fixed builtin identity, variants included, has a concrete `<id>:native-proof-ports` reason in `UNSUPPORTED_LEGS`, and only a fixed builtin may have one; every ACP
|
|
20
|
+
- Validate coherence and conformance enrollment for reviewer and headless variants too. Construct driven conformance peers through `start.create`; name dedicated coverage and concrete reasons for unsupported legs. Every fixed builtin identity, variants included, has a concrete `<id>:native-proof-ports` reason in `UNSUPPORTED_LEGS`, and only a fixed builtin may have one; every ACP factory product must report `deliveryOnly === true`, `hubOwned === true` and `form === "headless"` at runtime admission and in the conformance kit; never mark the mandatory `restrictive-ports` leg unsupported. Do not restore removed placeholder launch, lifecycle, init or permission metadata.
|
|
21
21
|
|
|
22
22
|
## Permissions, modes and approvals
|
|
23
23
|
|
|
@@ -29,6 +29,7 @@ Scope: the Codex, ACP and Pi adapters and the Pi extension (turn `generation` an
|
|
|
29
29
|
- Agent shell authority (#812, `classifyPeerCommand` in `src/cli/identity.ts`, `src/hub/operational-authority.ts`): the Allowed read forms live in `peerReadForms` and are admitted only when the daemon also admits the same form for an ordinary tools connection; the conductor family is the closed `conductorOperation` list, each admitted by `admitOperational` (role recheck, closed audit label, no argument text; `queue show`, a settings preview and the authorize handshake recheck through `operationalCurrent` without an audit record). Throw `OperationalAuthorityError` only from an authority fence: only it is audited as refused and gets the conductor or person guidance, and an ordinary operation failure keeps its own error (#839). A new CLI form is `console` until it is added to one of those tables with a test in `test/identity.test.ts`.
|
|
30
30
|
- A headless ACP Kimi or Codex whose mode lives in the native agent joins the bus only through `reconcileNativeStart` (`src/hub/daemon.ts`): after `start()` the daemon's current mode is applied, then `bus.add` (it can drain at once), then the state `start()` reached is replayed. Every mode change runs inside `permissionChange`, one per peer at a time.
|
|
31
31
|
- Generic permission and generation dispatch in the daemon reads the optional `PeerAdapter` ports (`src/hub/peers.ts`, #667): `permissionModeState`, `setPermissionMode` (resolves only after native acknowledgement; changes approval frequency, never the sandbox), `sessionGeneration` and `checkpointUsesSessionGeneration`; never add a native-class predicate (`isAcpPeer`, `isCodexPeer`) for it. An absent `sessionGeneration` keeps the recovery-session/owner-token fallback; a present but undefined one is unknown and refuses. `BasePeer` installs no default generation, and only an explicit `checkpointUsesSessionGeneration = false` (Pi) skips the checkpoint generation fence. A `permissionModeState` of `unknown` or `unmanaged` refuses a change.
|
|
32
|
+
- Delivery, lifecycle and session dispatch in the daemon reads the same optional ports (#625 slices 2b-2c): `deliveryOnly` (absent or `false` permits `hub_delivery_retry`; `true` or a present `undefined` refuses), `hubOwned` (only `true` lets `peer_stop` stop the runtime), `pendingOwnedProcess`, `form` (a present `undefined` is unknown: `peer_stop` refuses, and a definition with `permission.unmanagedForms` treats it as a TUI), `captureResume`, `detachForRoster`/`resumeFromRoster`, `pullOnly`, `recoveryReady` and `nativeSession` (rules: `daemon.md`). An adapter the hub owns and may stop sets `hubOwned = true`, a delivery-only transport sets `deliveryOnly = true`; never add an `is*Peer` call for any of them (ratchet above).
|
|
32
33
|
- Codex takes a mode as an `approvalPolicy` override on `turn/start`, and the override persists on the thread: pass the hub's and the TUI's `turn/start` alike through `applyPermissionPolicy`, and return to `ask` only by sending the captured native policy once. With none captured, refuse while the TUI is attached; never just stop sending the override. Keep the captured native policy and the restoration debt per thread (`nativeApprovalPolicies`, `overriddenApprovals`) across detach and `thread/start` or `thread/resume`; clear a debt only on a successful response to its restoring `turn/start` while the mode is `ask` (`settlePermissionRestore`), so a rejected turn keeps it. While a thread owes a restoration with a known baseline, in any mode, never replace that baseline: a `thread/resume` response and the TUI's own `turn/start` can both echo the sticky override, and the restoring turn sends the kept baseline whatever the TUI supplied. That protection is one turn: after the restoration succeeded, a policy the TUI sends is taken as the person's choice, captured and passed through, because the proxy cannot tell a repeated echo from one. A detached proxy's `clearPermissionMode` is never refused; if the native policy is unknown, return a CLI-visible note that the thread keeps its last policy. Attached refusal directs the person to close the TUI, then run `ahub permission codex ask`.
|
|
33
34
|
- An unanswered `session/set_mode` leaves the native mode unknown, not unchanged: `AcpPeer` invalidates the turn, goes offline, stops its process group and refuses further changes until restarted. Never report the old or the requested mode after a timeout.
|
|
34
35
|
- A mode grant in `piPermit` or the local worker's `permit` (`src/hub/daemon.ts`) answers as the prompt path does: allowed only while that owner is still the attached one and the call was not aborted. `ask-when-needed` covers `PI_EDIT_TOOLS` on a `grantablePath` only (path rule: `local-worker.md`).
|
package/docs/agent-notes/bus.md
CHANGED
|
@@ -24,7 +24,7 @@ Scope: delivery, digests and condensation, reply addressing, priority and limits
|
|
|
24
24
|
- `resolveDelivery` mutates queues in place; a refused or failed resolution puts back only that peer's queue and preface and keeps every pause. Never reload a snapshot on a live bus. Keep the selected delivery id and journal revision fixed in console row actions; retry/discard/completed never change a task role or cancel a native turn.
|
|
25
25
|
- Recovery-deferred hub notices (`deferredHub`, #478) are accepted work kept outside the queues and the carried-queue integrity projection: never put them in `queues` or the integrity view while held. Every read of pending work includes them (`pendingEnvelopes`, `withdraw`, `revokeSupervision`, `queuedBacklogIds`, `get`, reset). Past `MAX_DEFERRED_HUB` refuse the publish; never drop a kept notice.
|
|
26
26
|
- Release moves each deferred notice through `publishNow` and checkpoints it before delivery, events or nudges run. A failed checkpoint restores only publication state (the affected queue arrays in place, deferred, seen and withdrawn ids, pending outcomes) and keeps the hold. Never reload a snapshot or touch prefaces, pauses or the recovery mode from it. A successful release clears the deferral mode (`deferHub`) after `flushDeferredHub()` and before dropping `recoveryHeld`, so a later bare hold or `fenceRecovery` queues hub notices; a failed flush keeps both the hold and the deferral mode (#541).
|
|
27
|
-
- Do not offer recipient retry to delivery-only local
|
|
27
|
+
- Do not offer recipient retry to delivery-only transports (`PeerAdapter.deliveryOnly`, set by the local worker and every ACP product) or pass ordinary messages through their hold; their hints direct the person to resolve. Keep current-adapter refusal authoritative and the ephemeral MCP exposure marker advisory; pull-only Kimi TUI retains retry.
|
|
28
28
|
- Route peer retry only through `retryDelivery`: scope explicit-retry history by peer and immutable original ids across all grouped parents, commit monotone consumed-proof inheritance for every grouped non-context original with the checkpoint while keeping resolution audit rows unchanged; exclude hub `presence` originals from every retry proof, warning, inheritance and unknown-marking path, preserve unknown imported lineage fences without disabling fresh work, and admit busy-peer consent only to requeue its own `needs_review` row. Decorate rendered clones after condensation/pull/steer, never originals; reserve the shared journal body cap for the warning and an explicit Unicode-safe cut marker. Keep held hints bounded, body-free and advisory; metadata failure never changes a saved tool result or refusal class (#434).
|
|
29
29
|
- Periodic and status paths (roster, supervision, held-delivery views) read only pending journal state: `pendingPeers()`, `queueList(peer, true)` (`unresolved`) and `revision()`, and `snapshot(false)` when only queues are needed. Never call `list()` or build a journal `snapshot()` there for a revision; they sort the whole payload history (`test/delivery-journal.test.ts`, #719). Explicit queue and history commands and recovery snapshots keep the full history.
|
|
30
30
|
- `restore` takes queued work from the journal, never from the committed snapshot once the journal has state (an empty journal, revision 0, is seeded from the commit): from the commit only an empty queue key is carried over, for an offline peer whose key no delivery ever persisted (#234). Never resurrect a non-empty queue from it.
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Daemon, control protocol and recovery
|
|
2
2
|
|
|
3
|
-
- Keep external-channel behavior in `src/adapters/channel-peer.ts`; the daemon owns socket authentication, admission and generation binding.
|
|
3
|
+
- Keep external-channel behavior in `src/adapters/channel-peer.ts`; the daemon owns socket authentication, admission and generation binding. In core, dispatch through `PeerAdapter` ports; call an adapter-owned native type predicate only at a site with a row in `test/daemon-native-branches.json` (rule: `adapters.md`). Preserve the Codex `unidentified-cancellation` status notice while its attached session is latched; clear it on verified reattachment or TUI detachment.
|
|
4
|
+
|
|
5
|
+
- Claude channel launch and permission-generation proof (#625 slice 2e) goes through the daemon-bound `createClaudeLaunchProof` in `src/hub/claude-launch-proof.ts`. Keep `owner` gated by fixed-builtin definition identity and actual `WsPeer`; a generic adapter generation never proves it. Keep the explicit headless child `currentImplementerTools` path and terminal execution-root record checks separate. Preserve legacy hello/facts admission and managed-record precedence over the matching terminal row. A native type predicate call in `src/hub/claude-launch-proof.ts` is counted by the native branch ratchet (rule: `adapters.md`).
|
|
6
|
+
|
|
7
|
+
- Session/recovery dispatch (#625 slice 2c) reads `PeerAdapter.nativeSession`, never `definition.session.currentId`: local and reviewer context/memory provenance stays unbound. Keep Claude's session/claim proof in the daemon and `lastCodexThreads` as the resume-command fallback. An absent `recoveryReady` is settled; a present false or undefined value refuses. Call an idle owner's `captureResume` before recovery inspect/prepare/commit; preserve snapshot and protocol shapes.
|
|
4
8
|
|
|
5
9
|
- For explicitly user-authorized live verification, use the per-invocation CLI delegation in `docs/security.md` §Agent CLI identity and conductor authority; keep caller markers and record native evidence separately from fixtures.
|
|
6
10
|
- Keep `export const LIVE_TEST_SUPPORTED = true;` in `src/cli/identity.ts`: delegated recovery staging reads this declaration before any source effect.
|
|
@@ -12,7 +16,7 @@
|
|
|
12
16
|
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`, `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`, `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.
|
|
13
17
|
|
|
14
18
|
- 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).
|
|
15
|
-
- `peer_stop` is console-only and stops only
|
|
19
|
+
- `peer_stop` is console-only and stops, through its adapter, only an owner whose adapter reports `hubOwned === true` and whose `form` is not `tui` (today headless ACP, Pi, local, print-reviewer and Claude implementer); a present `undefined` form refuses. Refuse native-terminal peers and recovery-held mutations; serialize with starts and permission changes, and refuse a recovery `prepare` while a stop runs. An admitted stop holds delivery through `BusOptions.deliveryHeld` before any teardown await (never a console pause that resume can undo, or a global recovery hold that blocks unrelated peers) and withdraws the peer's pending approvals before the adapter stops. A requested stop holds an in-flight delivery as `needs_review` with an ids-only stop reason, suppresses restart/escalation, and never resolves that delivery automatically.
|
|
16
20
|
- Console review guidance (#711): `r` on a task in review opens `reviewChoice` (`src/cli/console-review.ts`, modelled on `modeChoice`) from the row or the open task detail, never a bare prefill; `a` and `c` prefill `review N approved|changes_requested ` with a guide line, a `changes_requested` line with no note is refused in `reduceConsole` before any effect, and the result of a review command is shown as a notice in panels mode (`reviewResult`). The console only prefills and runs `ahub review` as the person: never grant it extra authority, and keep a private task's title out of the choice line (`[pii]`). The CLI cause line (`agentShellCause`, `src/cli/identity.ts`) is printed for the `tools` identity and a person-only hub refusal only, never for the console identity, and never replaces the refusal.
|
|
17
21
|
- Console changed-symbols block (#684 2b): `symbolFields` (`src/cli/console-symbols.ts`) adds `symbols` after `plan` for a task_show-shaped detail only, from the public `codeSymbols`; never from a raw `Task`, never for a private task, never from a stored receipt (one with `repositoryKey` reads as unknown: only the screened public receipt is shown), never as a second copy of the match rule (`planSymbolMatches` in `review-brief.ts`). Unknown is `unknown (no code facts)`, never an empty list.
|
|
18
22
|
- Console Queue `D` (#713) only prefills `queue discard --peer <selected row's peer> --reason `; Enter refuses a missing peer or reason and arms a `y` confirm that names the count, then runs the person's `ahub queue discard ... --yes`. The `queue` op stays console-only; `discard` without `confirmed` only counts, and a confirmed one needs a reason, is refused during recovery or shutdown and discards row by row through `resolveDelivery` (rule: `bus.md`).
|
|
@@ -7,6 +7,7 @@ Scope: the hub-native local worker, its tools, path guard, denylist and seatbelt
|
|
|
7
7
|
- A timeout or abort in `sandboxedExec` marks the result `(killed: <signal>, timeout?)` from its own `killRequested` flag, never only from the exit signal: a shell may report its killed child as a normal exit 137.
|
|
8
8
|
- The sandbox denies home reads by default (toolchain dirs, the project and its real git dir excepted) and all network, loopback included: claude-mem and the Codex app-server listen on loopback without auth. Tests that bind a local port therefore fail when the worker runs them, also with `local.bash_network: true` (the egress proxy keeps loopback closed, and the proxy variables send a test's plain-HTTP `fetch` to the proxy, which refuses it); that is the intended trade-off, and `"direct"` is the switch until a later release removes it.
|
|
9
9
|
- The seatbelt profile refuses the `.git` name itself at any depth (creation, rename, symlink, gitfile), `config`, `config.worktree` and `hooks` directly under it, `commondir` at any depth below it or below an external git dir, and `.agenthub` anywhere in the project, every name folded against the 16 code points HFS+ ignores (#270). Every `modules/` tree the project can reach (the common dir's, the worktree's own, every linked worktree's under `<common>/worktrees/<wt>/modules/` as a pattern so a tree or worktree that does not exist yet is covered, #518, and each discovered submodule's own `modules/`) refuses EVERY write that is not inside a discovered submodule git dir (#520): nothing is exempt by name, so a fake git dir called `refs`, `logs` or anything else cannot be planted for a later unsandboxed `git submodule update --init` to reuse. Inside each discovered git dir `D` the refusals are anchored to `D`: `D/config`, `D/config.worktree` (folded in both halves), `D/hooks` and what is below it, and `D/worktrees/*/config.worktree`, with `D/worktrees`, each `D/worktrees/<name>` and `D/hooks` refused as directory entries too, so none can be moved out, edited and put back; a submodule's own linked worktrees keep a `modules/` of their own at `D/worktrees/<name>/modules`, which gets the same everything-not-discovered refusal by pattern (so one that does not exist yet is covered) and has its `modules` entry refused as a target; `D/refs`, `D/logs`, `D/objects`, `D/index` and `HEAD` stay writable, so commit, branch, tag, gc and rebase work, and a submodule really named `refs` or `logs` is protected like any other. `hooks` is anchored by path depth (`hooksUnder`), never by an unanchored any-prefix name, which also matched a project under a directory named `hooks`. The worktrees container and each worktree git dir are refused as directory entries too (otherwise an ancestor can be moved out of the protected path, edited and moved back), and so are the discovered git dirs, the modules root and the components in between (rename, replace, symlink, recreate). Keep every regex literal under 1024 bytes (UTF-8 bytes, not characters), and never put the git path and a folded name in one literal: the root is its own `require-all` filter, so the folded literals are the same size at any path length (`test/sandbox.test.ts` asserts it). Known residuals: a nested repository in bare layout (the hub never runs git there; docs/security.md), folded spellings of the components between `.git` and a submodule's git dir on a volume that folds names (the 1024-byte SBPL literal cannot hold a folded path; external git dirs carry the same residual since #270), and the submodules of a repository nested inside the project (only the project's own common dir is discovered). An external git dir's `config`/`config.worktree`/`hooks` rules, and the pattern rules for every linked worktree git dir under `<common>/worktrees/`, cover only its own children plus the hooks directory's contents: at any depth they would deny refs named `config` or `hooks` in a worktree or submodule project. The fold is a group alternation, never a character class (the seatbelt regex engine is byte-oriented and a quantifier after a multi-byte class member binds to its last byte), and every pattern keeps its `^` anchor (an unanchored starred alternation does not match). Folded literals never contain the project root: an SBPL string literal dies at 1024 bytes. The cost: `git init`, `git clone` and `git worktree add` do not run inside the sandbox, and neither do `git worktree remove`, `git worktree prune` of a stale record, `git clean -ffd` with a nested repository, `git submodule update --init` (its git dir's `config` write is refused), or `rm -rf` of a directory holding a `.git`.
|
|
10
|
+
- The shared object store `<common>/objects` takes only NEW loose objects from a sandboxed command (`objectStoreRules` in `src/local/deny.ts`, #864): every write under `objects/info` (alternates, http-alternates, commit-graph) and `objects/pack` is refused, so are the `objects`, `objects/info` and `objects/pack` entries, the unlink or rename of a fan-out directory, and data, unlink, mode and flags writes to an existing loose object. Keep loose-object creation allowed (sandboxed `git add`, `commit`, `stash` need it) and never reopen packs or `objects/info` for a sandboxed command: git reads packs before loose objects and never rehashes on read, so either one replaces content that every checkout and the #845 host add/commit/push trust. `test/sandbox.test.ts` "(#864)" proves it with the real sandbox-exec. The cost: `git gc`, `repack`, `fetch`, `maintenance` and `commit-graph write` run outside the sandbox. A hardlink to an existing loose object is refused too (into the project and into the command's own temp dir); keep that case in the test. Residual: an object id that does not exist yet can be planted before git writes it; a local checkout then shows the planted bytes, and a push of that commit is rejected by the receiving side, which rehashes what it receives ("missing necessary objects"). The upgrade path is host-side git writes as in #845.
|
|
10
11
|
- Build every git invocation outside the sandbox through the single implementation exported by exact-pinned `@staix/agent-mem/platform` (`memoryGitArgv`/`memoryGitEnv`/`memoryGitSync`), re-exported as `hubGitArgv`/`hubGitEnv`/`hubGitSync` by `src/hub/git.ts`. Keep the git export identity, behavior and direct-spawn tripwire checks in `test/hub-git.test.ts` passing; retain home compatibility and Node-safe process-entry checks in `test/memory-parity.test.ts`. The sandboxed git tool in `src/local/tools.ts` retains its own behavior. Use `{ credentials: true }` only for the memory repository the hub creates itself, never a project's repository. Use the core deny policy re-exported by `src/local/deny.ts` for guards, sandbox rules and capture; do not add another denylist.
|
|
11
12
|
- SBPL strings go through `sbplString` (the plain `"..."` form). In the raw `#"..."` form a backslash escapes nothing, so a `"` in a path ends the literal and the whole profile fails to parse.
|
|
12
13
|
- Seatbelt sees absolute paths, so `local.deny` entries are anchored under the project root.
|
|
@@ -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` or `src/hub/
|
|
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` or `src/hub/ci-watch.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.
|
|
@@ -19,6 +19,8 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
19
19
|
- Capture the coordination preference once per task at creation (`coordinationModes` from `TasksDeps.coordination`, in `propose` and for each split child, #439). `formCohort` joins only overlaps with the same captured mode and forms a silent cohort only for a turn-free one; a reload never silences an existing task or lifts an existing silent cohort. A daemon restart re-pins retained rows to the startup mode (a compatibility ceiling, not task metadata). `configureSweep` takes effect at the next pass: a running `sweep` keeps the config it started with.
|
|
20
20
|
- The planner "board changed" notice (#758, only with the lifecycle on) is built from what was added to a card's history since the last scan (`Tasks.groomNotices`, `groomScan`), never from the status revision alone: skip the peer whose own entry it was (`entry.by`), tell only the card's planners (`taskPlanner`, #525-2), send a planning revision or dependency change (a `planning` entry, or `deps` that differ from the last scan whatever wrote them: a propose with `after`, a split) and a card's creation (a peer's propose, a split child, a generated card) at task weight, since new work and its dependencies are what a plan review consolidates, and replace an unsettled copy (`sendGroomNotice` withdraws it by the `The board changed` prefix) so a planner meets the latest revision once, and send state moves, reviews and assignments at `fyi` weight (recorded, no turn), at most once per planner per `GROOM_FYI_WINDOW_MS`. The hub's own bookkeeping entries (`by` is `hub` with no `planning`: lifecycle links, idle sweeps, holds, plan observations) tell nobody and spend no window; a deps change with no new entry has no known author, so no planner is skipped for it. Scan every configured planner, not only the attached ones, so a change made while one was offline is kept for it. The first notice a planner gets after a hub start is task weight. Keep the scan out of the status revision and never read history that is not new.
|
|
21
21
|
- Planner work-ready notices (#785 P1a, `writePlannerNotices`): a write that can free a reserved owner's WIP or release backlog (done, approval, decline, cancel, backlog return or pull, untouched return, pull undo) takes `capacityBefore` (the `wip()` of the task's owner and reservation) before its board write and calls `writePlannerNotices` after it. Each rostered planner of the task (`taskPlanner`) gets one ids-only `task` envelope starting `Planner work ready:`: `capacity_released: <peer> WIP <n>; reserved ready #<id>|none` for a peer whose WIP dropped, and, when the write approved the task, `backlog_ready: #<id>` for each backlog dependent with nothing left to wait for. Merge the lines of that planner's unsettled copies, read back from `bus.pendingEnvelopes` (they survive a restart; never keep them in memory), withdraw them, then publish, so at most one stays queued per planner; all of it runs inside `releaseNotice`. The notice only wakes the planner: never pull, reserve or assign from it, and never put a title or note in it.
|
|
22
|
+
- Opt-in auto pull (#785 P1c, `tasks.auto_pull`, `Tasks.queueAutoPulls`, `autoPull`, `topReservedReady`): `writePlannerNotices` collects the peers whose WIP this write lowered and, only when `TasksDeps.autoPull()` is true (read at each release, so a reload applies to the next one), queues one `autoPull(peer)` per peer on a promise chain, so two releases never pick the same card and each job re-reads the board. It takes the top backlog card explicitly reserved for that peer (`topReservedReady(peer, undefined, true)`, the rule the notice's `reserved ready #id` line also uses; never an unreserved card, never PII, which `topReservedReady` skips for a pull), finds the card's own planner of record (`taskPlanner(card)[0]`, which must be rostered) and calls `pullPlanned(HUB, input, { onBehalfOf: planner })`. `onBehalfOf` is accepted only with `by === HUB`; the planner's authority is what `planningAuthority` checks, the actor in history and the planning telemetry stays `hub`, and the planner binding records the planner it acted for. Every other check is `pullPlanned`'s own, unchanged, and a refusal is one console notice with the public title (never detail). A held or recovering hub, a storage error or the setting off pulls nothing, and that is checked twice: at entry and again by `pullPlanned`'s `opts.guard` (`autoPullAdmitted`) wherever authority is rechecked, which includes right before the assignment or board write after the awaited PII screen and `planReviewGap`, so turning the setting off or starting recovery or shutdown mid-admission writes nothing and reports nothing. `autoPullsPending()` counts queued and running pulls and `recoveryReady` waits for it (also named in the recovery view's blockers), as it does for `checksPending`. The key is in `ALL_MACHINE_LOCAL` (`config-trust.ts`) so a committed config is ignored for it, and it is not in `CONDUCTOR_SETTING_KEYS`: an agent never turns it on. Never add a timer or sweep that pulls: the only trigger is the write that freed the slot.
|
|
23
|
+
- Per-head CI watcher (#785 P1b, `src/hub/ci-watch.ts`, `Tasks.watchCi`, `ciCompleted`, `reofferHeldReview`): when a `done` leaves a review whose admission was not ready at the task's exact full-commit head (or a review is held as premature in `sendReview`), `CiWatchers` starts at most one watcher per task and head, with chained unref'd `setTimeout`s and no interval. Each poll is one `gh pr view` through the `sdlc.gh` port (`pollCi`); it ends on the first passed, failed or incomplete result for the required checks, on `CI_WATCH_DEADLINE_MS`, on `stopCiWatchers` (daemon `stopOnce`), or silently when `ciWatchCurrent` fails (the task left `in_review`, its head or PR changed, the lifecycle is off, it turned private). `complete`, `review` (both verdict writes) and `cancel` cancel it. A head reports once (`settled`); a newer head replaces the watcher. The result is one ids-only `ci_completed:` task envelope at `status` weight to the owner and the reviewer (`fyi` is never delivered to a peer, so it would not reach them), a console notice for `user`, and for a deadline a console notice only. A passed or incomplete result re-offers a review held as premature once (`heldReviews`, keyed by task, head, reviewer and review revision) after a fresh `reviewRequestAdmission` says ready; a failed one re-offers nothing. No task text goes in the notice, no PII task is watched, and nothing is restored after a hub restart (the next `hub_task_done` starts one). Never add a second periodic gh read outside this watcher.
|
|
22
24
|
|
|
23
25
|
- Completed-change notices (#713, one per recipient per done, accepted work only, superseding by `completedFor`): rule in `bus.md`.
|
|
24
26
|
- Plan-symbol overlap (#682 1b) compares through `symbolsOverlap` (`src/hub/symbol-normalizer.ts`): arguments stripped, `section `/`source ` keys exact, two qualified names equal, otherwise last segments equal. `places`, `overlapHits`, `contradict`, the completed-notice replay, `plannerGrooming` and `planSymbolMatches` all use it; never compare plan symbols with `includes` or set membership.
|
|
@@ -86,7 +88,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
|
|
|
86
88
|
|
|
87
89
|
- Planner walk and reviewer independence (#525-3): `plannerWalk` and `implementers` in `src/hub/routing.ts` are pure; the planner is chosen only inside `assign()` (`planners`, `namedPlanner`, `keptPlanner` in, `Assignment.planner`/`plannerReason` out), and `Tasks` never picks one outside it. `Tasks.weights()` is the one place `planners` is built (rostered holders of `d.roles`, sorted), so every `assign()` path and `explain` agree. Pass a bound task as `keptPlanner` to that same walk: its explanation names the binding as `kept`; drafts and legacy null bindings still rotate, and explanation never writes a binding or history. Rotation is `task.id mod n` over every holder and is never renumbered by availability; a named planner is exact and never replaced. I-2 (`implemented this task`) lives in `blocked()` for the reviewer role as its LAST check, so the legacy, named and pool paths share it and it only ever removes an otherwise eligible candidate; when I-2 removed every otherwise eligible reviewer, the person reviews with `reviewBlocked`, never an implementer.
|
|
88
90
|
- Owned implementer child (#747): `propose`, `acceptReviewed`, `done` (through `complete()`) and `remember` take `requestCheck(by)` (`TasksDeps.requestAuthority`) at entry and call it after every await, before the next write; a new await on those paths adds a call, and its refusal stays pre-effect. The child's tool surface is the fixed `IMPLEMENTER_HUB_TOOL_NAMES` (`src/hub/hub-tools.ts`); never widen it from project roles.
|
|
89
|
-
- Shell branch binding (#845, `Tasks.bindShellBranch`): the daemon's host shell wrapper path is its only caller, and it is the only writer of the hub's `shell branch bound` entry (the branch and a sha256 of the worktree root, setting `refs.branch` in the same write). It binds only for the current owner of an `in_progress` or `changes_requested` non-PII task in this project's repository, outside a private turn, whose verified execution root (`TasksDeps.executionRoot`) is not the project root and is the binding's worktree. It refuses a branch that differs from an earlier binding or from `refs.branch`, or that another open task in the same repository holds, and rechecks `current()`, the private turn and the flow revision right before the write. Every failure returns `false` (no grant), never a throw. It records host-observed evidence, never a branch a tool call claims, and grants no execution by itself.
|
|
91
|
+
- Shell branch binding (#845, `Tasks.bindShellBranch`): the daemon's host shell wrapper path is its only caller, and it is the only writer of the hub's `shell branch bound` entry (the branch and a sha256 of the worktree root, setting `refs.branch` in the same write). It binds only for the current owner of an `in_progress` or `changes_requested` non-PII task in this project's repository, outside a private turn, whose verified execution root (`TasksDeps.executionRoot`) is not the project root and is the binding's worktree. It refuses a branch that differs from an earlier binding or from `refs.branch`, or that another open task in the same repository holds, and rechecks `current()`, the private turn and the flow revision right before the write. Every failure returns `false` (no grant), never a throw. It records host-observed evidence, never a branch a tool call claims, and grants no execution by itself. The entry is in `QUIET_EVENTS`: it neither moves nor settles the task, so it never makes a running check's result late, supersedes an assignment or counts as a move for review admission (#880).
|
|
90
92
|
- Deliverables (#509a, `src/hub/deliverables.ts`, `src/hub/deliverable-files.ts`): the hub hashes each file itself under the owner's verified execution root (`TasksDeps.executionRoot`, the daemon's `ownerWriteRoot`; an unproven root refuses); never accept a caller's hash, size or unknown field. Vet and hash before any board write and refuse pre-effect; a `done (checking)` carries its records to `complete()`, never a later read. An approval rereads the delivered files: a reviewer's verdict is refused before any write unless every file is `unchanged`, and only the person approves moved files, which are rehashed and named in the note. A file the hub cannot read or vouch for is `unknown`, never `unchanged`. Peer-facing records go through `publicDeliverables`: for a PII task or a `pii.paths` file, id, hash and size only.
|
|
91
93
|
|
|
92
94
|
- Task/release planners (#525-2): keep nullable `planner` columns additive, with no backfill. Bind new proposals and null-planner pulls only from `assign()`; install the fixed synchronous `Board.proposalPlanner` host port so proposal, split-child and generated-card bindings enter the original insert transaction before notification. Preserve explicit null callers and existing rows. Preserve existing bindings on owner moves. `Tasks.taskPlanner` keeps a bound rostered settled holder even while offline/paused; a missing, removed or off-roster holder falls back to legacy holders. Every task/release planner effect and its post-await validation uses that fresh scope; project-wide reads remain holder-wide. Naming a planner is person-only, refused for closed or PII work, admits only the holder `assign()` returns for `namedPlanner`, and records only `planner changed`, never an owner delivery event. Split children intentionally run their own pure rotation and may bind a different planner from their parent; do not inherit the parent binding. A removed/off-roster binding is preserved as legacy metadata on pull without recording a false `kept` reason.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Tests
|
|
2
2
|
|
|
3
|
-
Scope: tests, fakes and the test gate. Read before adding or changing a test or fake under `test/` or `src/adapters/conformance/`, or editing `scripts/check.sh`, `scripts/hang-watch.sh`, `scripts/progress-tee.sh`, `scripts/seeded-check.ts`, `scripts/seeds.json`, `scripts/lib/ts-ast.ts`, `scripts/changed-tests.mjs`, `scripts/check-closing-refs.mjs
|
|
3
|
+
Scope: tests, fakes and the test gate. Read before adding or changing a test or fake under `test/` or `src/adapters/conformance/`, or editing `scripts/check.sh`, `scripts/hang-watch.sh`, `scripts/progress-tee.sh`, `scripts/seeded-check.ts`, `scripts/seeds.json`, `scripts/lib/ts-ast.ts`, `scripts/changed-tests.mjs`, `scripts/check-closing-refs.mjs`, `.github/workflows/closing-refs.yml`, `scripts/release-prepare.ts`, `scripts/release-tag.ts` or `scripts/lib/release-plan.ts`.
|
|
4
4
|
|
|
5
5
|
- A fake's gated action that never sees its gate skips the action; never fall through to a late, unsynchronized action. Owned-group tests inject `AcpOwnedGroupPorts`; a test never signals a process group it did not spawn.
|
|
6
6
|
- When extracting production closures into a VM fixture, bind every referenced host port and actual imported policy helper. Keep native identity checks on real unstarted instances; never replace a policy predicate with an always-true stub or weaken existing assertions to accommodate a moved dependency.
|
|
@@ -19,11 +19,11 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
19
19
|
- A request that a test expects a stop or shutdown to cut gets its rejection handler where the request is created, never after the awaited teardown: a late `catch` races the reset and the run fails on an unhandled rejection.
|
|
20
20
|
- Run `bun scripts/seeded-check.ts` before reporting the seeded-guard gate complete. After changing seeded guard logic, run its affected seeds even when search-anchor counts are unchanged. Put asynchronous calls inside `resolves`/`rejects` expectations so a mutated refusal fails a named assertion. Keep the `scripts/seeds.json` cases sequential; require the same named test green before its seeded assertion fails. Seed rot, survivors, setup/compiler failures, timeout/watchdog termination and process leaks fail the gate. A change that moves or rewrites a line a `scripts/seeds.json` case searches for updates that case's search and replacement in the same PR (seed rot fails the gate), keeping its mutant effect and named test. Never run the full seed runner from a unit test or mutate the source checkout; inspect preserved failed fixtures before removing them. CI runs it as four sequential Linux shards (`--shard i/4`) beside the ordinary check, separately from `scripts/check.sh`; the required `seeded guards` job passes only when every shard passed, or when the decision job reused a verified identical-tree full gate (`admission == 'reuse'`). Keep that job name: branch protection and `scripts/ci-gates.mjs` read it (#457). On seed rot the checker names the anchor's symbol at a base commit and where it is now (`seedRotContext`): `--base <commit>` sets that base (CI passes the PR base from a `fetch-depth: 0` checkout; keep both), and without it the base is `merge-base origin/main HEAD`. The context is evidence only: it never relaxes the one exact match, and an unavailable base says so instead of guessing.
|
|
21
21
|
- `scripts/lib/ts-ast.ts` is the one TypeScript AST helper for scripts and source-scanning tests (`parseSource`, `callSites`, `symbolSpans`, `enclosingSymbol`, `findSymbol`; inline HTML scripts keep their offsets). It gives lexical locations only, never resolved call edges, and stays dev tooling: never import it from `src/`.
|
|
22
|
-
- The native branch ratchet (`test/daemon-native-branches.json`) is reviewed data, never regenerated by a test (rule: `adapters.md`). `test/daemon-native-proof-gates.test.ts` runs daemon code cut out of `src/hub/daemon.ts` by its text, from ` function
|
|
22
|
+
- The native branch ratchet (`test/daemon-native-branches.json`) is reviewed data, never regenerated by a test (rule: `adapters.md`). `test/daemon-native-proof-gates.test.ts` runs daemon code cut out of `src/hub/daemon.ts` by its text, from ` function frontendProxyState(` to ` const manualPaused =` and from ` async function changePermission(` to ` async function confirmPermissionDefault(`, and the ` const isSupportedPeer =` and ` const readOnlyPeer =` bindings: renaming, reordering or reindenting them updates the test in the same PR. Test Claude launch permission proof through `createClaudeLaunchProof` in `src/hub/claude-launch-proof.ts`; never restore the removed daemon wrapper just to slice it in a fixture.
|
|
23
23
|
|
|
24
24
|
- `scripts/check.sh` pins the test environment (#610): `GIT_CONFIG_GLOBAL=/dev/null`, `NO_COLOR` unset, `TERM=xterm-256color`, temp root under the OS per-user temp dir, agent markers cleared. A test that needs a person's git config or color settings sets them itself. `--changed` selects tests with `scripts/changed-tests.mjs`: changed tests (nested suites included) plus every test that names a changed file directly; docs need none; any other changed file no test names, and any gate, package or shared-fixture change, runs everything.
|
|
25
25
|
- `scripts/check.sh` holds the repo's heavy-job slot (`<git common dir>/ahub-heavy.lock`, #398) for the whole run, so a test never runs the real gate; `test/check-lock.test.ts` runs a copy in a scratch repo.
|
|
26
|
-
- The whole-suite watchdog is `scripts/hang-watch.sh PID SECONDS [PROGRESS_FILE QUIET_SECONDS]` (#115, #516). `scripts/check.sh` sends bun's stderr (where it prints results) through a fifo to `scripts/progress-tee.sh FILE`, which forwards every line unchanged and appends one byte to FILE per `(pass)`, `(fail)`, `(skip)` or `(todo)` line and per test file header; the watchdog stops the suite when FILE has not grown for `AHUB_CHECK_QUIET_S` (default 300, set in `.github/workflows/check.yml`, above the 120 s of the longest explicit test timeout). That is the hang detector, so a slow suite that keeps passing tests is never cut, and a test that logs without finishing is not progress. `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted values for both OSes in `.github/workflows/check.yml`; #378, #593) is only the backstop for a run that keeps printing results while hung: raise it, with the job's `timeout-minutes` (room above it for setup), only when a healthy run nears it, never for a hung test, and keep the per-test 20 s timeout. The two-argument form (no file) is the old total-time bound, still used by the CI timing job and `scripts/seeded-check.ts`. A fifo and a filter of its own, never a pipe (`$!` must stay bun's pid) and never a process substitution (it cannot be waited on with a bound); `scripts/progress-tee.sh` is in the `--changed` FULL list. `test/hang-watch.test.ts` covers the watchdog, the filter and the fifo wiring.
|
|
26
|
+
- The whole-suite watchdog is `scripts/hang-watch.sh PID SECONDS [PROGRESS_FILE QUIET_SECONDS]` (#115, #516). `scripts/check.sh` sends bun's stderr (where it prints results) through a fifo to `scripts/progress-tee.sh FILE`, which forwards every line unchanged and appends one byte to FILE per `(pass)`, `(fail)`, `(skip)` or `(todo)` line and per test file header (under mawk only with `-W interactive`: mawk reads a pipe in blocks, so without it nothing counts until the input ends, #883); the watchdog stops the suite when FILE has not grown for `AHUB_CHECK_QUIET_S` (default 300, set in `.github/workflows/check.yml`, above the 120 s of the longest explicit test timeout). That is the hang detector, so a slow suite that keeps passing tests is never cut, and a test that logs without finishing is not progress. `AHUB_CHECK_HANG_S` (default in `scripts/check.sh`, hosted values for both OSes in `.github/workflows/check.yml`; #378, #593) is only the backstop for a run that keeps printing results while hung: raise it, with the job's `timeout-minutes` (room above it for setup), only when a healthy run nears it, never for a hung test, and keep the per-test 20 s timeout. The two-argument form (no file) is the old total-time bound, still used by the CI timing job and `scripts/seeded-check.ts`. A fifo and a filter of its own, never a pipe (`$!` must stay bun's pid) and never a process substitution (it cannot be waited on with a bound); `scripts/progress-tee.sh` is in the `--changed` FULL list. `test/hang-watch.test.ts` covers the watchdog, the filter and the fifo wiring.
|
|
27
27
|
- A seed's named test must run on Linux, where the seeded job runs: a darwin-only test reports no result there and fails the job (#382). Cover a macOS-only protection with its regression test instead.
|
|
28
28
|
|
|
29
29
|
- The `closing-refs` workflow (`.github/workflows/closing-refs.yml`, #760) runs `scripts/check-closing-refs.mjs` (PR number from argv or `PR_NUMBER`) on `opened`, `edited`, `reopened` and `synchronize`: it reads `gh pr view --json closingIssuesReferences,body,title,commits` and fails when GitHub would close an issue the title and body only mention with `Refs`, a negation or a qualifier (`Refs #15. Does not close #15.` and `Refs #15. Partially fixes #15.` fail, `Refs #15.` and `Fixes #15` pass), or when a commit message has a closing keyword, negated or not, for an issue the title and body do not mean (a squash merge carries it). An issue linked only in the sidebar passes. A keyword is meant only when nothing earlier in its sentence (ending at `.`, `!`, `?`, `;` or a line break, never a comma) matches `NEGATION`; widen that list, never narrow it to let a qualified close pass (background: README). `mentioned`, `keywordReferences`, `intentionalCloses`, `closingMisuse` and `commitMisuse` are pure and covered by `test/closing-refs.test.ts` with the real `gh` shape (`repository.name`, `repository.owner.login`); its command test puts a fake `gh` first on `PATH`, never the real one. Run it by hand with `GITHUB_REPOSITORY=<owner/repo> node scripts/check-closing-refs.mjs <number>`.
|
|
@@ -39,5 +39,6 @@ Scope: tests, fakes and the test gate. Read before adding or changing a test or
|
|
|
39
39
|
- Headless-reviewer tests run `test/fakes/fake-claude-print.ts` and headless Claude implementer tests run `test/fakes/fake-claude-implementer.ts` through `command` or a PATH stand-in (the implementer with `HOME` and `CLAUDE_CONFIG_DIR` in a temp dir), and fresh Codex reviewer tests run `test/fakes/fake-codex-reviewer.ts` through `codex_bin`; a test that reaches the daemon's reviewer or implementer start must never run the real `claude` or `codex`. The fake's sandbox markers test host admission only, never native isolation. Fresh Kimi reviewer tests set and restore `KIMI_CODE_HOME` to a fixture native home (the backend links that home's `credentials/`, `oauth/` and `device_id`, else the person's `~/.kimi-code`) and run `test/fakes/fake-kimi-reviewer.ts` through `command` or a PATH stand-in, never the real `kimi`. Qwen TUI tests run `test/fakes/qwen-dual.ts` (scripted by submit text) as the launched Qwen or the daemon's `qwen_cmd` (an adapter unit test also passes its own `table`), never the real `qwen`. agy tests put a fake `agy` first on PATH and set `HOME` to a temp dir before any transcript readback (`readAgyTranscript` reads `$HOME/.gemini`). Code-facts tests build `new CodeFacts({ env })` with a PATH whose first `ripwire` is a wrapper around `test/fakes/fake-ripwire.ts` (`FAKE_RIPWIRE_MODE` selects its failures), or inject Tasks' `prepareReviewBrief`; never rely on an installed ripwire, which CI does not have.
|
|
40
40
|
- A Pi fake the daemon starts through `createPi` answers `--version` with a version `COMPAT.pi` verifies, as `src/adapters/conformance/fakes/pi-rpc.ts` and `test/fakes/pi-data-policy.ts` do: the factory's preflight (`piVersionAsync`, #847) refuses the start otherwise.
|
|
41
41
|
- A daemon test that declares a headless Claude implementer in a repository whose `origin` is on GitHub passes `shellDefaultBranchProbe` (`DaemonOptions`, #845), or the start runs `git ls-remote` against github.com; a test of `readTaskShellDefaultBranch` itself puts a fake `git` first on the `PATH` it passes.
|
|
42
|
+
- A test of `scripts/release-prepare.ts` or `scripts/release-tag.ts` runs the script with its root in a scratch repository whose only remote is a local bare directory: set `AHUB_RELEASE_ROOT` to it, or copy the script into it as `test/release-tag-daemon.test.ts` does. Without that the script takes its root from its own location and fetches and runs `ls-remote` against this checkout's real origin. Consent comes from a fake port, a stand-in `AHUB_CLI` or a scratch daemon, never the person's hub.
|
|
42
43
|
- Production `MemoryClient` (`src/memory/client.ts`) is the in-process amem store and never opens `memory.worker_url` (#752). A test that needs the legacy worker shape injects `FakeMemoryClient` (`test/fakes/mem-worker.ts`) as `DaemonOptions.memoryClient` or in place of the client; never add an HTTP path to the production client for a test. An amem-backed test sets its own temp `AMEM_HOME` and reads only its own fixture memory directory.
|
|
43
44
|
- CLI print guards (#814, `test/cli-json-guard.test.ts`): two scans over every file in `src/cli`. One finds `console.log(JSON.stringify(...))` and passes only behind a `--json` branch (`selectsJson`), a named `DATA_FORMAT` or `NOT_YET_CONVERTED` entry. The other finds a print of a hub tool's text (`console.log(await taskOp(<op>))`, or a variable bound from one) and passes only behind `--json`, for a named `SENTENCE_OPS` op (checked against the daemon's case), or named debt (`TEXT_DEBT`, `CONDUCTOR_DEBT`, `REFUSAL_FALLBACK`). A list entry with no matching site fails, so the lists only shrink. Add a command's `--json` branch or renderer; never add a list entry to make a new print pass. Per-command output tests (`test/task-output.test.ts`, `test/sdlc-output.test.ts`) run the real CLI against a fake control server and assert default not JSON, `--json` byte equality, 80 and 200 columns and `--color=never`.
|
package/docs/dogfood.md
CHANGED
|
@@ -13,7 +13,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
13
13
|
| Message bus, digests, priorities | on | `ahub report`: messages, digests, overflow, undeliverable; delivery latency from events |
|
|
14
14
|
| Compact board (#724) | on | `ahub board` at 80, 120 and 200 columns: one line per task (test/board-compact.test.ts) |
|
|
15
15
|
| Backlog list table (#399) | on | `ahub backlog list` at a terminal width: one line per card by rank with the reserved owner and dependencies, and the count by owner (test/backlog-list-output.test.ts); `--json` for scripts |
|
|
16
|
-
| Native branch ratchet and
|
|
16
|
+
| Native branch ratchet and host proof ports (#625) | active in source/CI after merge; running daemon gate takes effect after release and upgrade | reviewed predicate/function/count/reason rows equal source; foreign native proof products and nativeRevert declarations refused; ACP delivery-only/owned/headless admission and generic delivery/stop/form/roster ports; plain-peer daemon tests cover session binding and recovery settlement, builtin context/provenance parity and ACP/channel notices; roster-detached Pi seed proves the recovery fence; Claude launch/generation proof uses the fixed-builtin real channel owner, with legacy hello/facts and non-native generation refusal tests; fixed-builtin frontend proxy/notice/revert ports have plain-peer and native debt/restore parity tests, with the action-policy Codex predicate intentionally retained |
|
|
17
17
|
| Review scope for sandboxed reviewers (#852) | on after the release for the fresh Codex and Kimi reviewers (default `code`); a person may set `review_scope` on an instance in `config.local.json` and restart the hub daemon | `ahub status` shows `review scope` on each reviewer row; a code reviewer's review request carries the one fixed line and no check or CI line (test/review-scope.test.ts); its approval history entry carries `reviewScope: code`; a `changes_requested` with no open finding is recorded `evidenceOnly`, adds nothing to `rejections`, and the conductor gets one ids-only notice (count the `evidenceOnly` history entries against the 7 verification-only rejections seen on #467, #512 and #510, and watch that no task escalates on them) |
|
|
18
18
|
| Headless Claude host shell grants (#845) | source/fixture work; active only after release and hub/plugin upgrade with `ask-when-needed` on the declared implementer | automatic wrapper grants versus person requests; native poisoned-environment and replay receipt; unknown/expired/reused/stale task, branch, root or child tickets execute zero commands |
|
|
19
19
|
| Optional model for a headless Claude implementer (#844) | on after the release and once the person sets `model` on `instances.claude-impl` in `config.local.json` (a hub daemon restart applies it); unset it changes nothing | `ahub status` shows `model source` on the instance row; the native argv carries `--model <value>` on every turn and none when unset (test/claude-implementer.test.ts, test/claude-implementer-daemon.test.ts); a tracked-file or non-implementer declaration is refused or ignored with a reason (test/instances.test.ts, test/jcode-instances.test.ts) |
|
|
@@ -57,6 +57,9 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
|
|
|
57
57
|
| Planner plan review (#481 S9) | advise | plans reviewed before build |
|
|
58
58
|
| Planner board-changed notice (#758) | on here, with the lifecycle in `advise`: `claude` is the planner, so a card another peer proposes or re-plans reaches it once at task weight, and its own writes, state moves, reviews and assignments cost it no turn | per planner and week: `task`-kind hub envelopes whose body starts `The board changed` (target: one per planning change, none after the planner's own write), the same envelopes at `fyi` priority (recorded, no turn: at most one per 10 minutes), and the count still queued at once (target: at most one). Read from `ahub log` and the event log; the 12 copies in 44 minutes seen on 2026-10-24 are the baseline |
|
|
59
59
|
| Planner capacity and dependency notices (#785 P1a) | source/fixture coverage; active after release and hub upgrade | write-triggered `capacity_released` and `backlog_ready` lines in `Planner work ready:` task envelopes; at most one queued envelope per effective planner, peer/WIP/card ids only; no automatic pull |
|
|
60
|
+
| Release preparation and guarded tag (#787 P3b) | scratch-repo fixtures with a local bare remote; the real history was dry-run in a disposable clone; first real use is the next release | `scripts/release-prepare.ts` run on a `release/<version>` branch and a release PR that carries the version, CHANGELOG, report and stamps; `scripts/release-tag.ts` refusing without the person's consent and pushing one tag after it |
|
|
61
|
+
| Opt-in auto pull (#785 P1c) | source/fixture coverage and a real daemon test; off by default, active only where a person sets `tasks.auto_pull` in `config.local.json` | `backlog pulled` history entries with `by: hub` and an `auto_pull:` note; at most one per capacity release; no pull with the setting off, for unreserved, PII, pinned, frozen or dependency-blocked cards |
|
|
62
|
+
| Per-head CI watcher and `ci_completed` (#785 P1b) | source/fixture coverage with a fake gh; active after release and hub upgrade | one `ci_completed:` task notice per head to owner and reviewer, a premature review re-offered once green; watchers started per `hub_task_done` head and gone at the result, deadline, newer head, verdict or shutdown; no gh read outside a watcher |
|
|
60
63
|
| Plan conformance (#481 S2) | advise | PRs with files outside the plan |
|
|
61
64
|
| Test integrity for bug fixes (#481 S4) | advise | bug PRs without a red-first receipt, modified pre-existing tests |
|
|
62
65
|
| Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
|
package/docs/memory.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hub memory integration
|
|
2
2
|
|
|
3
|
-
Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.
|
|
3
|
+
Agent-hub consumes the exact-pinned first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library (current pin `0.11.0`, initial pin `0.1.1`). The package owns portable storage, tiers, graph, goals, sync, import and raw commands. Agent-hub owns caller authority, current project privacy policy, task lifecycle integration and presentation. Claude-mem is an optional, explicit import source only; runtime recall and capture make no worker HTTP requests.
|
|
4
4
|
|
|
5
5
|
## Portable core and data directory
|
|
6
6
|
|
package/docs/operations.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Operations guide
|
|
2
2
|
|
|
3
|
-
This guide describes ahub 0.
|
|
3
|
+
This guide describes ahub 0.20.0 and control protocol 16. Live verification
|
|
4
4
|
results and remaining prerequisites are recorded separately in [the smoke ledger](smoke.md).
|
|
5
5
|
|
|
6
6
|
## Agent-operated live verification
|
|
@@ -914,7 +914,11 @@ create, rename or remove a `.git`: `git init`, `git clone` and
|
|
|
914
914
|
`git worktree add` do not run inside the sandbox, and neither do
|
|
915
915
|
`git worktree remove`, `git worktree prune` of a stale record,
|
|
916
916
|
`git submodule update --init`, `git clean -ffd` over a nested repository or
|
|
917
|
-
`rm -rf` of a directory that holds a `.git`.
|
|
917
|
+
`rm -rf` of a directory that holds a `.git`. The shared object store takes
|
|
918
|
+
only new loose objects (#864): `objects/info` (alternates, commit-graph) and
|
|
919
|
+
`objects/pack` are closed and an existing object cannot be changed, removed or
|
|
920
|
+
replaced, so `git gc`, `repack`, `fetch` and `maintenance` do not run inside
|
|
921
|
+
the sandbox either. `git add`, `commit`, `stash` and `checkout` work, and
|
|
918
922
|
`.gitignore` and `.github/` stay writable; a commit that your git configuration
|
|
919
923
|
signs with a key under `~/.ssh` or `~/.gnupg` fails, because the sandbox cannot
|
|
920
924
|
read those directories. Run a refused command yourself, in a terminal.
|
|
@@ -1363,7 +1367,7 @@ ahub memory forget 1a2b3c4d # a person's; also promote, demote, sup
|
|
|
1363
1367
|
|
|
1364
1368
|
`"memory": { "native": false }` in `.agenthub/config.json` turns it off for a project.
|
|
1365
1369
|
|
|
1366
|
-
The exact-pinned `@staix/agent-mem` 0.
|
|
1370
|
+
The exact-pinned `@staix/agent-mem` 0.11.0 resolves `AMEM_HOME`, then `$AGENTHUB_HOME/memory`, then `~/.agenthub/memory`. An absent `M/profile.json` selects shared v1; an explicit generation selects `M/generations/G` with the package's readonly legacy bridge. All daemon memory consumers and the hub memory/goal CLI use that selected profile. Invalid metadata or a missing selected generation makes native memory unavailable while the bus and board continue; there is no fallback to legacy writes. Inspect the daemon's memory binding/availability before interpreting missing memory as an empty store.
|
|
1367
1371
|
|
|
1368
1372
|
The daemon checks its selector at hub operation entry and before candidate/counter writes. It does not hot-switch handles or provide an atomic fence against selection changes during an in-flight core call. Quiesce consumers and close their handles before migration or selection changes; reopen through the supported session-preserving lifecycle afterward. Candidate/counter storage remains `M/candidates.db`, independent of `G`; since agent-mem 0.7.0 `amem review` also lists and accepts candidates on a selected generation. Every running hub using this home needs an authenticated receipt: a matching ready binding, or a generation-capable unbound receipt (`disabled` when native memory is off, `unavailable` when opening failed) proving it holds no memory handle. An unbound daemon and zero running hubs are quiesced for memory; a stale bound handle, unreadable registry or unknown runtime is not ready. Existing JSON memory/goal results remain unchanged; the additive binding receipt belongs to daemon status, profile-check and the decay object. This source integration does not authorize installing a new runtime, migrating real memory, restarting sessions or claiming real-machine sync/capture receipts. Migration/deactivation and their limits belong to [agent-mem](https://github.com/STAIxBWLB/agent-mem); see [the hub profile contract](memory.md#selected-memory-profile).
|
|
1369
1373
|
|
|
@@ -1910,6 +1914,7 @@ A guarded file edit that the daemon keeps at its accepted value remains visible
|
|
|
1910
1914
|
| Switches | `task_sweep.enabled`, `.auto_reassign`, `.interval_s`, `.unaccepted_min`, `.idle_min`, `.review_min`, `.ready_min`, `.ladder_min`, `.no_writes_turns` | `config.local.json` | live; `.no_writes_turns` sets the turn threshold independently; other keys reschedule the sweep timer |
|
|
1911
1915
|
| Routing | `tasks.wip_limits.<peer>` | `config.local.json` | live |
|
|
1912
1916
|
| Routing | `tasks.escalate_after` (1-10, default 3: consecutive `changes_requested` before a task moves to the next `escalate_to` peer) | `config.local.json` | live; the next verdict uses it |
|
|
1917
|
+
| Switches | `tasks.auto_pull` (default off: pull the top explicitly reserved ready card when a planner slot frees) | `config.local.json` | live; the next freed planner slot uses it |
|
|
1913
1918
|
| Roles | `planner` (one or up to eight planner holders) | `config.local.json` | at once |
|
|
1914
1919
|
|
|
1915
1920
|
- Writes go only to `.agenthub/config.local.json` and `.agenthub/routing.local.toml`,
|
|
@@ -2406,8 +2411,33 @@ releases backlog work (#785): a lower WIP for the owner or reserved peer adds a
|
|
|
2406
2411
|
approval that leaves a backlog card with no unresolved dependency adds
|
|
2407
2412
|
`backlog_ready: #<id>`. The lines go in one `Planner work ready:` task notice
|
|
2408
2413
|
per planner, ids only; a newer one merges and replaces the unsettled copy, which
|
|
2409
|
-
survives a restart.
|
|
2410
|
-
board and decides.
|
|
2414
|
+
survives a restart. By default nothing is pulled automatically: the planner reads the
|
|
2415
|
+
board and decides. With `tasks.auto_pull` on (opt-in, see below) the hub itself pulls
|
|
2416
|
+
the top card explicitly reserved for the peer whose slot just freed.
|
|
2417
|
+
|
|
2418
|
+
When `hub_task_done` leaves a review whose exact-head CI is not yet green (the
|
|
2419
|
+
lifecycle must be on, the task linked to a PR and not private), the hub starts one watcher
|
|
2420
|
+
for that head (#785): one `gh pr view` a minute, ended by the CI result, by a
|
|
2421
|
+
45 minute deadline, by a newer head, a verdict or a cancellation, or by hub
|
|
2422
|
+
shutdown; a restart does not restore it, and the next `hub_task_done` starts a new
|
|
2423
|
+
one. On the result the owner and the reviewer each get one
|
|
2424
|
+
`ci_completed: #<id> head <sha12> passed|failed|incomplete` task notice (ids only, the
|
|
2425
|
+
head cut to 12 characters; a person reviewer gets a console line instead), and
|
|
2426
|
+
unless CI failed, a review the hub held as premature for that head is offered
|
|
2427
|
+
once more after a fresh readiness read. A deadline tells the console only. No peer turn waits for
|
|
2428
|
+
CI, and nothing is polled outside a watcher.
|
|
2429
|
+
|
|
2430
|
+
`tasks.auto_pull` (default off, machine-local, live) lets the hub act on that
|
|
2431
|
+
wake-up (#785). When a write frees a peer's planner slot, the hub pulls the one top
|
|
2432
|
+
backlog card explicitly reserved for that peer, through the same planner pull a
|
|
2433
|
+
planner makes: revision, dependency, reservation, pinned, frozen-release, P0 order
|
|
2434
|
+
and WIP checks all apply unchanged, and a refusal becomes a console notice naming
|
|
2435
|
+
why. Unreserved cards, cards reserved for another peer and private (PII) cards are
|
|
2436
|
+
never pulled. One card per release, as the hub for the card's own planner of record:
|
|
2437
|
+
the history entry reads `by: hub` with that planner and the reason
|
|
2438
|
+
`auto_pull: capacity released for <peer>; hub acted for planner <planner>`. A hub that
|
|
2439
|
+
is paused for recovery pulls nothing, also when the setting is turned off or recovery begins while a pull is already waiting on its screening: that pull writes nothing, and a recovery waits for pulls in flight. A committed `config.json` cannot turn it on: like
|
|
2440
|
+
the other machine-local policies it is ignored there with a log line.
|
|
2411
2441
|
|
|
2412
2442
|
## Resetting a project's hub
|
|
2413
2443
|
|
|
@@ -2562,7 +2592,7 @@ section is what they run.
|
|
|
2562
2592
|
|
|
2563
2593
|
Upgrade running projects with the target release's own coordinator. It accepts
|
|
2564
2594
|
a running source on control protocol 9 (0.6.x), 10 (0.7.0 through 0.12.0),
|
|
2565
|
-
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.
|
|
2595
|
+
11 (0.12.1 and 0.12.2), 12 (0.12.3), 13 (0.12.4 through 0.12.15), 14 (0.12.16), 15 (0.12.17 through 0.12.19) or 16 (0.12.20 through 0.20.0), and only
|
|
2566
2596
|
a target on its own protocol, so the target's coordinator fits every supported
|
|
2567
2597
|
source and carries every recovery fix released up to it. Protocol 8 and older
|
|
2568
2598
|
(0.5.x and earlier) are refused as `manual-bootstrap-required`. Run from the
|
|
@@ -2576,7 +2606,7 @@ bunx --package @staix/agent-hub@0.12.43 ahub upgrade --to 0.12.43 --yes
|
|
|
2576
2606
|
| Running now | Coordinator to use |
|
|
2577
2607
|
| --- | --- |
|
|
2578
2608
|
| 0.6.x (protocol 9) | the target's, through `bunx` as above |
|
|
2579
|
-
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.
|
|
2609
|
+
| 0.7.0 through 0.12.0 (protocol 10), 0.12.1 and 0.12.2 (protocol 11), 0.12.3 (protocol 12), 0.12.4 through 0.12.15 (protocol 13), 0.12.16 (protocol 14), 0.12.17 through 0.12.19 (protocol 15), 0.12.20 through 0.20.0 (protocol 16) | the target's, through `bunx` as above |
|
|
2580
2610
|
| any supported source, with the installed CLI already at the target | `ahub upgrade` below, which is the same coordinator |
|
|
2581
2611
|
| 0.5.x or earlier (protocol 8 and older) | not supported: bootstrap by hand with the matching CLI |
|
|
2582
2612
|
|
|
@@ -4377,6 +4407,8 @@ The task's `refs.branch` is authoritative. Otherwise the first eligible task-com
|
|
|
4377
4407
|
|
|
4378
4408
|
Check/test/build cannot write the project Git common/current directory at all, including objects, HEAD/index, refs/logs, other worktrees and either submodule tree. Closed host add/commit reuse core Git hardening, ask with configured filters/external drivers/textconv, and force signing/automatic maintenance off. A global Git LFS installation does not disable push/gh grants; only add/commit keep person approval so LFS conversions are not silently bypassed. Automatic push still does not run pre-push hooks. Config includes and partial clones refuse privileged Git/gh. Their command/paths/current task/branch are re-proved before execution. The host pins the default branch from the remote at startup, using a bounded isolated-config Git symref probe against the captured GitHub repository. Local origin/HEAD changes cannot redefine it. No credentials are supplied to that probe; unavailable/private-remote evidence or an unsupported Git layout asks. local.bash_network "direct" gives code direct network access. A setsid-detached code child can outlive owned-group cleanup while retaining its sandbox and permitted worktree writes.
|
|
4379
4409
|
|
|
4410
|
+
Default-branch capture runs only at daemon startup when a Claude implementer is declared. If the first implementer is added through config reload, automatic task-command grants remain unavailable and those commands ask. Restart the daemon with that implementer declared to capture the remote default branch. Reload does not retry a failed startup probe or refresh the captured default branch.
|
|
4411
|
+
|
|
4380
4412
|
Project Git retains the core policy that clears credential.helper and core.askpass; an HTTPS push needing those helpers may fail, rather than importing credentials or relaxing that policy. Task branch/config proof reads are bounded synchronous reads (1.5 seconds per reader); slow or unknown evidence asks. The existing sandbox profile discovery is also synchronous; moving it to an asynchronous inventory proof with a shared deadline remains a follow-up. The nonce is visible in a permission response, so it proves a bounded grant rather than native-dispatch provenance.
|
|
4381
4413
|
|
|
4382
4414
|
The [native permission receipt](verification/2026-10-23-headless-claude-permissions.md) covers Claude Code 2.1.309, and the [2.1.310 receipt](verification/2026-10-26-claude-implementer-2.1.310.md) repeats it on 2.1.310 (with 2.1.309 as a control), each with a deterministic loopback fixture and no real account/model call. It is not sandbox proof or a runtime receipt gate. The real account/model/person interaction remains a separate live leg.
|
|
@@ -4500,7 +4532,7 @@ Native launcher connections retry transport timeouts/unclean closes with bounded
|
|
|
4500
4532
|
|
|
4501
4533
|
## amem runtime (agent-mem#13 P4)
|
|
4502
4534
|
|
|
4503
|
-
The hub exact-pins @staix/agent-mem 0.
|
|
4535
|
+
The hub exact-pins @staix/agent-mem 0.11.0 for recall, curated evidence and local deterministic capture. No runtime call reaches a claude-mem worker; `memory.worker_url` is ignored. The person's `ahub memory import claude-mem` remains explicit and off by default. Existing profile, alias, PII and private-turn fences still apply. `ask --remember` and context checkpoints propose machine-local candidates, never automatic curated evidence. See [memory integration](memory.md#in-process-recall-and-capture-agent-mem13-p4) for changed behavior and bounded-view ceilings.
|
|
4504
4536
|
|
|
4505
4537
|
## Backlog issue identity and notes
|
|
4506
4538
|
|
package/docs/quickstart.md
CHANGED
|
@@ -17,7 +17,7 @@ ahub setup # installs the Claude Code channel plu
|
|
|
17
17
|
Or use the matching GitHub release:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
bun add -g github:STAIxBWLB/agent-hub#v0.
|
|
20
|
+
bun add -g github:STAIxBWLB/agent-hub#v0.20.0
|
|
21
21
|
ahub setup
|
|
22
22
|
```
|
|
23
23
|
|
|
@@ -30,6 +30,10 @@ and upgrade or crash recovery, see the [operations guide](operations.md). To
|
|
|
30
30
|
move running projects to a newer release later, run `ahub upgrade` in a
|
|
31
31
|
terminal: it reviews the plan with you, applies it and follows it.
|
|
32
32
|
|
|
33
|
+
## Upgrading to 0.20.0
|
|
34
|
+
|
|
35
|
+
The hub now exact-pins `@staix/agent-mem` 0.11.0. Every `amem` that shares a memory home must run 0.11.0 or later to keep consolidation review receipts, so upgrade a global `amem` CLI too. When `hub_task_done` leaves a review whose exact-head CI is not yet green, the hub watches that PR head once a minute for up to 45 minutes and tells the owner and the reviewer once with a `ci_completed:` task notice; no peer turn waits for CI (#785). The new `tasks.auto_pull` setting is opt-in and off by default, and a committed config cannot turn it on: set it in `.agenthub/config.local.json` or a settings session, and when a peer's planner slot frees the hub pulls the top backlog card explicitly reserved for that peer (#785; see [the planner](operations.md#the-planner-455)). The local worker and Pi sandbox now lets a command add only new loose objects to the shared git object store (#864): `git gc`, `repack`, `fetch` and `maintenance` no longer run inside it, so run them yourself in a terminal. A task's host-observed branch binding no longer turns a running check result into "check finished late" (#880). Upgrade running hubs with `ahub upgrade` and restart Claude sessions so they load the 0.20.0 plugin. Coming from 0.18.0 or older, the notes below apply too.
|
|
36
|
+
|
|
33
37
|
## Upgrading to 0.19.0
|
|
34
38
|
|
|
35
39
|
Pi is now a gated CLI (#843): `ahub agents` lists it, 1.4.0 is the verified version, and any other version is refused at every launch path with the exact `ahub agents trust pi <version>` command. If your Pi is not 1.4.0, trust that version (person only) or install 1.4.0 before Pi work resumes. At hub start and once a day the hub reads each gated CLI's installed version without network, tells the planner once per id and version that is not verified, and `ahub status` shows an `agent CLI drift` line until it is (#843). Newly verified: agy 1.4.7, Jcode 0.99.1, headless Qwen 0.26.1, the Kimi fresh reviewer 2.3.2 and the headless Claude implementer 2.1.310. In `ask-when-needed`, a headless Claude implementer now runs proven read-only shell commands and a closed set of task commands (`scripts/check.sh --changed`, `bun run build`, `bun test <test files>`, `git add|commit`, `git push` and `gh pr view|checks|create` on the task's bound branch) through a host-owned wrapper with single-use tickets; check, test and build run in the local worker's sandbox, any other command and any call in an enabled always-ask class still ask, and an older plugin keeps asking (#845; see [additional headless peer instances](operations.md#additional-headless-peer-instances)). It may also name its model with `"model"` on its declaration in untracked `config.local.json`, applied at the next hub daemon restart (#844). The fresh Codex and Kimi reviewers now declare review scope `code`: they review code only, a `changes_requested` with no open structured finding is recorded as evidence-only and does not count toward escalation, `ahub status` shows `review scope` on each reviewer row, and `"review_scope"` on the instance in `config.local.json` overrides it (#852). Planners get one `Planner work ready:` notice when a write frees WIP capacity or makes backlog work ready; nothing is pulled for them (#785). The remote check runner installs dependencies once per lockfile key into a read-only volume and refuses a commit whose `package.json` declares an install lifecycle script (#793). A conductor operation whose hub connection or authorization handshake is unavailable now says so instead of reporting a role refusal (#812). The hub sandbox also protects a linked worktree's submodule git dirs and `config.worktree`, and refuses writes under a modules root outside a discovered git dir (#518, #865). A Qwen TUI background-task notice no longer ends a hub turn as uncertain (#517). `@staix/agent-mem` is pinned at 0.10.0. Upgrade running hubs with `ahub upgrade` and restart Claude sessions so they load the 0.19.0 plugin. Coming from 0.17.0 or older, the notes below apply too.
|
|
@@ -116,7 +120,7 @@ The restarted Claude planner gets the release-train tools (`hub_release_new`, `h
|
|
|
116
120
|
|
|
117
121
|
Jcode must be 0.98.0 or later, below 1.0.0; 0.94.0 is no longer accepted, so `ahub doctor` fails its Jcode row for an older install and the peer does not start (#529). Every owned jcode process runs with `JCODE_NO_TELEMETRY=1`.
|
|
118
122
|
|
|
119
|
-
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.
|
|
123
|
+
Since 0.12.37 the hub opens a split memory home through its shards (#534); the current exact `@staix/agent-mem` pin is 0.11.0. Before anyone runs `amem migrate --shards` on a memory home, every hub using that home must run 0.12.37 or later: an older hub does not see the split and keeps writing the old layout. `ahub memory profile-check` is split-aware and compares each hub with its own project's shard (#536). See [memory profiles and rollout](memory.md#selected-memory-profile).
|
|
120
124
|
|
|
121
125
|
From 0.12.30 or earlier, existing Jcode project MCP entries need one refresh before conductor or dashboard starts. Once per project, the person runs:
|
|
122
126
|
|
|
@@ -183,7 +187,7 @@ More: `ahub help`. How it is built and why: [`docs/specs/2026-09-19-agent-hub-de
|
|
|
183
187
|
|
|
184
188
|
## Shared memory
|
|
185
189
|
|
|
186
|
-
The hub exact-pins the first-party `@staix/agent-mem` 0.
|
|
190
|
+
The hub exact-pins the first-party `@staix/agent-mem` 0.11.0 core, including deterministic capture; no claude-mem worker is needed. Existing shared-v1 memory remains the default; upgrading does not migrate it. Managed agents use screened `hub_memory_search`, `hub_memory_show` and `hub_memory_graph` tools, or propose a correction with `hub_memory_correct`; only a person accepts candidates through the supported `amem review` path. Since agent-mem 0.7.0, `amem review` also accepts candidates on a selected generation.
|
|
187
191
|
|
|
188
192
|
Before any real generation migration, upgrade every hub sharing that memory home to 0.12.30 or later and check its authenticated binding with `ahub memory profile-check` in each custom registry. Ready bindings prove compatibility, not an atomic live cutover: quiesce consumers and close their handles before migrating, then reopen through the supported session-preserving lifecycle. Invalid or changed profiles disable native memory without falling back to legacy writes; the bus and board continue. See [memory operations](operations.md#hub-memory) for the remaining rollout gates.
|
|
189
193
|
|
package/docs/sdlc.md
CHANGED
|
@@ -357,6 +357,18 @@ Before other release commands, agents run `ahub sdlc release-guard -- <literal a
|
|
|
357
357
|
|
|
358
358
|
Tracked npm cleanliness compares the cached index to HEAD and raw tracked blob bytes without `git status`, clean/process filters or text converters. It refuses unknown evidence, gitlinks, transformed files and inventories above 4,096 entries, 4 MiB per file, 32 MiB total or 250 ms; untracked files remain outside this check and the eventual tarball is not attested.
|
|
359
359
|
|
|
360
|
+
## Release preparation and the guarded tag (#787 P3b)
|
|
361
|
+
|
|
362
|
+
Release preparation is an agent task; the release decision is not. They are two records and two commands, and consent stays person-only.
|
|
363
|
+
|
|
364
|
+
`bun scripts/release-prepare.ts [--version X.Y.Z] [--dry-run]` is the mechanical half of AGENTS.md "Releasing" steps 1 and 2. On a `release/<version>` branch (it refuses `main`, a detached HEAD, a dirty tree and any other branch name) it reads the commits since the last `v*` tag, applies the rule (any `feat` subject bumps the minor, otherwise the patch; a version below the rule or not newer than the last tag is refused, so is a missing or empty `## Unreleased`), and writes `package.json`, the README `Status:` line, the CHANGELOG section (the Unreleased entries move under the version, Unreleased stays empty), the bundle (`bun run build`) and a verification report skeleton `docs/verification/<date>-<version>.md`. The skeleton lists the pull requests the commits name and the native acceptance receipts, which it reads from `docs/verification/` files added since the tag, never from what an agent writes. It leaves `TODO(agent)` markers for what only a review can state: each PR's review and CI, the document review, the checks and the known gaps.
|
|
365
|
+
|
|
366
|
+
It restamps nothing by itself. A stamp in `docs/verified.json` records that someone read what changed under a document, so `bun scripts/release-prepare.ts restamp <document>... --reviewed` sets `verifiedAgainst` to HEAD for the named documents only, refuses without `--reviewed`, refuses an unnamed or unknown document and refuses with staged changes. The agent runs it per document after reading the source commits, commits `docs/verified.json`, opens the release PR and merges it with a merge commit (never squash).
|
|
367
|
+
|
|
368
|
+
`bun scripts/release-tag.ts <version> [--execute]` is the one guarded tag run. Every run repeats the whole preflight and reports every refusal at once: a valid release version; `package.json`, the README Status line and a CHANGELOG section at HEAD all naming it; HEAD on `main`, clean, and the merge commit of a `release/<version>` PR (a squash is refused); HEAD an ancestor of `origin/main` (fetched, not assumed); no `v<version>` tag locally or on the remote; `scripts/check-docs.mjs --require-fresh --committed` clean; and exactly one `docs/verification/<date>-<version>.md` with no `TODO(agent)` left. Without `--execute` it only reports, and whether the person's consent matches (exit 2 when it does not). With `--execute` it reads the consent through `ahub sdlc release-guard -- git push <remote> <commit>:refs/tags/v<version>` (read only; the ledger `ahub sdlc authorize-release` fills, person only, revoked by a hub restart), repeats the preflight, checks the commit did not move, reads the consent once more, and then makes the single push of that exact commit. A rejected push is reported and never retried, and the script passes neither `--force` nor `--no-verify`; the pre-push hook from `ahub init --release-guard` still runs on that push. The push creates a lightweight tag (`<commit>:refs/tags/v<version>`), the exact form `ahub sdlc release-guard` parses and the consent receipt binds; the repository's tags are mixed (v0.19.0 and v0.18.0 are annotated, v0.17.0 is not) and `.github/workflows/release.yml` accepts either (`v*`, then `--verify-tag`), so this is a convention difference, not a gap. If the project wants annotated release tags, create the tag object first and push the tag ref, which the guard also accepts (its source object is peeled to the commit); that is a later change to this script. `AHUB_RELEASE_ROOT` points both scripts at a checkout other than the one they live in (the tests use it so nothing outside a scratch repository is touched). Nothing here calls `authorize-release`, and no agent shell can: the report only names the command. Publication (npm, the GitHub release) stays with the tag-triggered workflow, which has its own gate (`.github/workflows/release.yml`).
|
|
369
|
+
|
|
370
|
+
The scripts and their pure helpers live in `scripts/release-prepare.ts`, `scripts/release-tag.ts` and `scripts/lib/release-plan.ts`; they are maintainer tooling and are not part of the published package. The deterministic recovery barriers of #787 AC3 are not needed by either leg and are not part of this change.
|
|
371
|
+
|
|
360
372
|
## Deliverable records (#509a)
|
|
361
373
|
|
|
362
374
|
A report, translation or slide deck has no commit, so the artifact identity is the file's own content hash, recorded by the hub.
|