@staix/agent-hub 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ Issue and pull request numbers in the entries for 0.7.7 and earlier refer to the
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.27.0
8
+
9
+ - Let the configured conductor record release consent on the person's explicit version-naming instruction (#1063). Receipt and audit retain the real asker; exact candidate/docs/origin bindings, restart revocation and live-test refusal remain.
10
+ - Record inert experiment protocols, plans, attempts, events and signed runner receipts through `hub_experiment_record` (#932, task 615). Existing Tasks authority and verification apply; PII turns refuse, raw arguments/results stay out of memory capture, and the relay starts no experiment.
11
+ - Concurrent `ahub up` cold starts tolerate an atomically replaced runtime record, while malformed, linked and oversized records still refuse (task 616).
12
+ - Read recorded research experiments (task 602, #932 T3): `ahub study experiments --campaign <id>` (or `--attempt`, or `--plan` with `--baseline` and `--variant`) and the read-only `hub_experiment_show` render a campaign's attempts and one comparison. Every value shows whether it is declared, host-observed, runner-attested or person-attested, failed and cancelled attempts stay in the counts, a repeat is never an independent reproduction, and an incompatible comparison is shown, not ranked. A synthetic CPU-only fixture (`test/research-experiments-fixture.test.ts`) covers baseline, variant, failure, changed-protocol, private-input and inaccessible-output cases, and `docs/operations.md` has a person-authored optional rerun checklist. Nothing here runs a command or records an experiment.
13
+
7
14
  ## 0.26.0
8
15
 
9
16
  - Retain each project's verified runtime across stop/start, preserve other projects when upgrading one, prune unreferenced source captures with reference checks, and report stopped projects before source preservation (#1039 follow-up). Runtime selection protection begins with the 0.26.0 resolver.
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.26.0, control protocol 17 (accepts 16). Bun + TypeScript, one loopback daemon per
8
+ Status: 0.27.0, control protocol 17 (accepts 16). Bun + TypeScript, one loopback daemon per
9
9
  project. Site: [agent-hub.staix.net](https://agent-hub.staix.net).
10
10
 
11
11
  ## Quick start
@@ -64,7 +64,8 @@ become links. The scene carries labels and states only, never message bodies or
64
64
  (a planner drafts it with `hub_study_draft`); only the person approves it, which
65
65
  creates one task per unit, and `ahub study show` reads coverage per unit and
66
66
  source. A unit is covered by its owner's `hub_study_handoff` with evidence, not by
67
- a done task.
67
+ a done task. Experiment attempts are recorded with `hub_experiment_record` and read
68
+ with `ahub study experiments`, each value naming where it came from.
68
69
  - **Shared memory.** Decisions, contracts, fails and findings are kept through the
69
70
  first-party [`@staix/agent-mem`](https://github.com/STAIxBWLB/agent-mem) library,
70
71
  in process with no claude-mem worker (short- and long-term tiers, a typed graph,
@@ -126,7 +127,7 @@ uses the home must run a version that understands the new layout.
126
127
  [#258](https://github.com/STAIxBWLB/agent-hub/issues/258)).
127
128
 
128
129
  Live checks that still need a real account or a person are listed in the
129
- [release verification report](docs/verification/2026-11-01-0.26.0.md) and the
130
+ [release verification report](docs/verification/2026-11-01-0.27.0.md) and the
130
131
  [smoke checklist](docs/smoke.md).
131
132
 
132
133
  ## Documentation
@@ -139,7 +139,7 @@ Scope: the operator console, control WS and its protocol, state files, the dashb
139
139
  - Campaign dispatch (#597): keep `research_dispatch` additive to `research_records`; any stored campaign requires it, because old campaign stop policies also need current dispatch enforcement. Preserve the conditional dispatch metadata/handoff integrity digest and every other recovery feature guard. A study-dropped owner offer (#603) is a `research_dropped_offers` row written through `Board.recordResearchDroppedOffer` from `Tasks.relevant`, never an in-memory map: a restart between the drop and the approval must still re-offer. It joins `researchDispatchIntegrity` only when rows exist (so a project without one keeps its digest), requires a `research_units` row for its task and a peer-shaped owner, and `studyReoffer` clears it only after the offer is published or the record is stale.
140
140
 
141
141
  - Experiment storage and runner registration (#598, #600): preserve `research_experiments`, its conditional integrity digest and every earlier research/calendar/autonomy guard. Read check evidence only through this project's host reader; no record input creates a measured field. Accept `research.runners` only in untracked `.agenthub/config.local.json`; refuse shared or tracked raw keys even when empty. Keep registration person-only, with whole-block tier2 structural edits and atomic scoped reload; never admit conductor writes. Consume registrations through `TasksDeps.researchRunners`, revalidating the active accepted configuration and current source trust each call; never adopt raw disk edits through that getter. While stored runner state or an active/raw runner key exists (empty counts), require `research_runners`. Include `researchRunners` in release-equality integrity only when stored receipts exist, using their count and stored-row digest alone; never bind accepted registrations or raw config presence to board-state equality across restart.
142
- - `ahub study` (#601, `src/cli/study.ts`, `src/cli/study-output.ts`): `study.ts` is pure argv parsing (ids `rc-` and twelve hex digits, revisions as positive integers, `--reason` takes the rest of the words) and runs before any connection; `main.ts` reads the contract file against `invokedFrom` with a 64 KiB bound and sends it to the hub, which validates it. `show` is `allowed` in `classifyPeerCommand` through `peerReadForms`; draft, revise, approve, approve-protocol and close are `console`, and the audit label names at most the subcommand (approve-protocol is labelled `study`; a campaign id, a protocol hash and a contract path are the person's text). The default output is for a person and `--json` prints the hub's text byte for byte (`test/output-cli.test.ts`); a new `study` form needs its `--json` branch and a renderer, never a `TEXT_DEBT` entry.
142
+ - `ahub study` (#601, `src/cli/study.ts`, `src/cli/study-output.ts`): `study.ts` is pure argv parsing (ids `rc-` and twelve hex digits, revisions as positive integers, `--reason` takes the rest of the words) and runs before any connection; `main.ts` reads the contract file against `invokedFrom` with a 64 KiB bound and sends it to the hub, which validates it. `show` and `experiments` (#602: `--campaign`, `--attempt` or `--plan --baseline --variant`, the same read as `hub_experiment_show`) are `allowed` in `classifyPeerCommand` through `peerReadForms`; draft, revise, approve, approve-protocol and close are `console`, and the audit label names at most the subcommand (approve-protocol is labelled `study`; a campaign id, a protocol hash and a contract path are the person's text). The default output is for a person and `--json` prints the hub's text byte for byte (`test/output-cli.test.ts`); a new `study` form needs its `--json` branch and a renderer, never a `TEXT_DEBT` entry.
143
143
 
144
144
  - Experiment comparisons (#599): preserve the separate conditional `research_comparisons` floor and integrity for plans, exact protocol/evaluator approvals and causal protocol-context rows. Never backfill legacy order from timestamps or carry approval to a changed hash. Keep the approval op under the console-only `study_` dispatch guard.
145
145
  - Doctor memory capture (#1008, #1025): the `memory capture: <tool>` rows come only from `amem setup <tool> --json`, a preview that writes nothing (`readMemorySetup` in `src/cli/main.ts`: no shell, 10 s, 256 KiB), judged by the pure `nativeMemoryCaptureRow` (`src/adapters/definitions/native-cli-doctor.ts`). A plan not in the documented shape, a non-zero exit, a timeout or unparsable output is unknown, never ready; never read dotfiles text or a client's config, and do not scrub the caller's identity before the call.
@@ -78,7 +78,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
78
78
  - `review.same_family = "never"` (#820) is read only inside `assign()` through `reviewPool.sameFamily`, which `Tasks.health()` and `reviewPoolOptions` pass from `d.review.same_family` (absent for the default `"fallback"`, so default routing stays byte-identical). It removes every same-family reviewer: the selector's last-resort loop (`selectReviewerPool` `sameFamily`), the legacy `automatic` fallback, a named same-family reviewer (refused with code `family`, `same family as <owner>; review.same_family is never`) and the planner escape unless the planner holds the reviewer role and is of another family. Nobody left means `reviewer = "user"` with `reviewBlocked`, never `sameFamilyFallback`, and that holds for any automatic exhaustion (all offline, no candidate, no declared instance), never `reviewer: none, done will approve directly`; the person fallback is keyed on the setting, not on an eligible same-family candidate. Keep it out of call sites: `Tasks` never tests the family itself.
79
79
  - `Tasks.retryReviewer` is host-only (the daemon's reviewer `onUnavailable`), never a tool or CLI path. It acts only while the failed peer is still the reviewer of an `in_review`, non-PII task, with the same review head, the request's `reviewRequestRevision` and a current child generation; `valid()` rechecks all of it after every await and right before the write, which also passes the flow revision. A manually named reviewer's `no_verdict` and a no-verdict with no declared pool go only to the person; other manually named failures keep explicit person control. Confirm fallback only after accepted reviewer publication or a person offer and a fresh binding/generation check; failed publication or notification never confirms settlement. Each failed peer is tried once per head and round (`reviewerFailure` history). `reviewRequestRevision` counts only role events (`done`, `reviewer changed`, `reviewer unavailable`, ownership), so bookkeeping never fences a request; keep `refs.reviewRevision` and `refs.commit` on the review envelope.
80
80
  - Release trains (#481 S11): change trains only through `Tasks.newRelease`, `configureRelease`, `assignRelease` and `cutRelease` (planner or person), and refuse every `hub_release_*` in a PII turn. `assignRelease` writes only `release` and refuses a frozen, shipped or dropped release on either side; private or pinned scope is person-only. One active or frozen train at a time. A cut freezes only through `Board.freezeRelease` after a fresh `releaseWindow` (host lease from `readReleaseHostAdmission`, quota, `current()`, at most 5 s old); unknown evidence defers, and the window is closed in `finally`. A cut never publishes, tags or upgrades, and upgrade and recovery never consult `releaseWindow`. Keep two of `RELEASE_TRAIN_HISTORY_LIMIT` slots for cut and ship; a configuration no-op appends nothing. An S9 plan receipt survives only contiguous planning revisions that changed exactly `["release"]`.
81
- - Release authorization (#481 S6, `src/hub/sdlc-release-authorization.ts`): only the person's console `sdlc authorize-release` creates one, bound to the current HEAD commit, its committed package version, the origin identity and a fresh, error-free docs verdict, all reread after the docs check. It lives in daemon memory only: a restart revokes it and the event is audit, never restored. `release-guard` and `release-guard-hook` are refused in a private turn; a tag push needs a matching receipt, and npm publish also a clean tracked worktree and the same docs digest. Never accept authorization from a tool, a task receipt or a file.
81
+ - Release authorization (#481 S6, `src/hub/sdlc-release-authorization.ts`): the console user or the one configured conductor through `sdlc authorize-release` creates one under the same admission as `sdlc merge` (tools role, matching configured conductor, no `toolsOwner`, private turn or caller PII context). Keep the actual asker in receipt `by` and the audit, and the cooperative explicit-person-instruction rule in AGENTS.md Releasing. Keep consent bound to the current HEAD commit, its committed package version, the origin identity and a fresh, error-free docs verdict, all reread after the docs check. It lives in daemon memory only: a restart revokes it and the event is audit, never restored. `release-guard` and `release-guard-hook` are refused in a private turn; a tag push needs a matching receipt, and npm publish also a clean tracked worktree and the same docs digest. Never accept authorization from a tool, a task receipt or a file.
82
82
  - `Board.update`'s untouched-return exception is the only way owned or accepted work returns to backlog: keep every condition (exact event, owner and reviewer null, state, `expectedFlowRevision`, non-empty note, no work evidence) and reach it only through `Tasks.returnUntouchedToBacklog` (#481 S9).
83
83
  - The main-checkout note (`mainCheckoutWarning`, `src/hub/main-checkout.ts`, #572) is advisory text in the `hub_task_done` result, the review request and status, never a refusal. A branch name or other metadata the hub reads from outside the board reaches task text only after `Tasks.publicMetadataText` (PII patterns, secret shapes, private paths) and a plain ref-name check; otherwise use the generic sentence that carries none of it.
84
84
 
@@ -110,7 +110,7 @@ Scope: the task flow, assignment, the state machine and the hub's MCP tools. Rea
110
110
  - Schedules (#512a, `src/hub/schedules.ts`, `Tasks.proposeFromSchedules`): the hub proposes a scheduled task only through `Tasks.propose` as `HUB`, never `Board.propose`, so the PII screen, class validation, triage and routing apply. "Its previous task is open" is read from the board (`openScheduled`: first history entry by the hub, detail starting `Schedule key: <id>`, state not closed), never from `schedule_runs`. A run restarts the period at `now`; a hold or an open task counts a skip and proposes nothing, so a long hold ends in one task. Status rows carry ids, times and a state, never the title or detail. A schedule is `every` or `cron` (+ `tz`), never both (#1017): every consumer of its period goes through `scheduleNext(entry, anchor)` and `scheduleZone`, never `parseEvery(entry.every)` (`every` is absent on a cron entry), and a view carries `cron` and `zone` instead of `every`. The cron core is pure and takes its time as an argument; keep the clock and the machine zone out of it, and keep the daylight-saving rule (a skipped time runs at the next valid minute, a repeated one once) in `instantOf` alone. No third-party cron or time-zone library.
111
111
  - Research campaign dispatch (#930, #597; `docs/specs/2026-11-01-research-campaigns-design.md` "T2 dispatch and coverage implementation boundary"): a task bound to a campaign unit reaches an owner only through the paths that call `studyDispatchReason` / `studyDispatchCheck` (offer, assign, planner pull, owner change, acceptance, and a queued owner offer re-checked at delivery). Each reads the stored campaign, its deadline and stop limits, and for a hard cap the named budget, synchronously, and `assignOwner` asks again immediately before the owner write with nothing awaited between that read and the write; a new owner write asks the same read there rather than checking earlier. An explicit request refuses before any effect; the hub's own offer holds with `holdReady`. Budget enforcement is the named budget by id (`ExecutionBudget.covers`), never `applies` (any budget), and it only reads: a campaign never resets, copies or writes a counter, and a `kind: "run"` budget stays peer-wide. A read that fails holds, never dispatches, and a hold never touches a native turn already running. `studyShow` returns ids, enums, counts and times, and strips source ids from a private campaign. A later slice that exposes a new dispatch or wire requirement adds its own `DAEMON_FEATURES` label and recovery guard.
112
112
  - Campaign storage (#930 T1, #596; spec "T1 implementation boundary"): every study write runs inside `studyWrite` (counted by `studiesPending`, which `recoveryReady` and the shutdown drain read; `stopStudies` fences new ones) and asks `requestCheck(by, () => this.studyHold())` again after every await and right before the board write. `studyApprove` screens and builds units outside any transaction, re-reads the latest hash and references, then writes once through `Board.activateCampaign`; never move a screen, source lookup or file read into that transaction. A replay creates nothing (it may only tighten privacy), a stable unit id keeps its task only for an identical definition (changed work takes a new id), and a private campaign never retains a public task. `research-campaign.ts` validates only inert plain data (plain prototype, no proxy, accessor or symbol key; a caller's `toJSON` never runs) before canonical hashing. A receipt carries ids, revision, status and counts; no raw contract or close reason enters parent history or memory.
113
- - Experiments (#932, #598, #599; spec "Experiment T1 implementation boundary" and "T2 comparison implementation boundary"): `Tasks.experimentRecord` and `experimentShow` have no hub tool or CLI yet; `study_approve_protocol` (`experimentApproveProtocol`, person only) is the one wired op, and a new tool or form needs its own admission and recovery review. A writer is the current in-progress owner of a unit task in the protocol's current approved campaign revision (`Board.researchDispatchBinding`), rechecked after every screen, receipt read and hash. A host measurement is its own `research_attempt_observations` row, never a rewrite of a declared event, and no path upgrades a provenance level. Output-path privacy uses `matchesPolicy` with `failClosedOnDiscoveryLimit` (`experimentOutputPrivate`); keep that opt-in to experiments, never the default for other callers. Approval binds the exact protocol hash and evaluator hash, never carries to a changed hash, and a comparison never ranks.
113
+ - Experiments (#932, #598, #599; spec "Experiment T1 implementation boundary" and "T2 comparison implementation boundary"): `hub_experiment_record` and `hub_experiment_show` are thin callers of `Tasks.experimentRecord` and `experimentShow`; the person alone approves through `study_approve_protocol`. Forward the complete record envelope and authenticated actor unchanged, keep record calls out of read-only publication, refuse all PII-turn contexts, and exclude their arguments/results from capture. Only the existing receipt kind admits a person relay; ordinary records keep the current in-progress unit-owner check. New forms need their own admission and recovery review. A writer is the current in-progress owner of a unit task in the protocol's current approved campaign revision (`Board.researchDispatchBinding`), rechecked after every screen, receipt read and hash. A host measurement is its own `research_attempt_observations` row, never a rewrite of a declared event, and no path upgrades a provenance level. Output-path privacy uses `matchesPolicy` with `failClosedOnDiscoveryLimit` (`experimentOutputPrivate`); keep that opt-in to experiments, never the default for other callers. Approval binds the exact protocol hash and evaluator hash, never carries to a changed hash, and a comparison never ranks.
114
114
  - Registered runner receipts (#1018): admit a new signed receipt only through `Tasks.experimentRecord`, with current task/source/campaign/privacy authority and fresh trusted registrations rechecked after screening. Keep declared, host-observed and runner-attested evidence separate; a deliverable protocol never derives a code match from a signed commit claim. Identical canonical receipt/signature replay returns stored proof without registration verification or writes, even after revocation; changed bytes or a new revoked/unknown-key receipt refuse. Validate the whole linked attestation event and absence of host observations on every stored receipt read, including paged views that omit that event. Store receipt/event and advance format 2 in the same transaction; keep new/unused databases at `HUB_DB_BASE_SCHEMA` (1). Preserve task, privacy and source fences and show current key status separately from as-of-signing provenance. No public recording surface runs or contacts a runner; keep relay arguments/results out of memory capture.
115
115
  - Study readback (#930 T3, #601): `ahub study draft|revise|approve|close` and the hub tools are thin callers of `Tasks.study*` and add no board write. The daemon's person ops are `study_draft|revise|approve|approve_protocol|close` (`taskOpBody`), refused on any non-console connection by an explicit `study_` check in the `task` control case before `Tasks` is asked; agents use `hub_study_draft` and `hub_study_revise` (configured planner through `planningAuthority`, never approve), `hub_study_show` (read-only, also in `READ_ONLY_HUB_TOOLS`) and `hub_study_handoff` (the bound in-progress owner). Pass the tool's arguments to `Tasks` whole: `studyFields` refuses an unknown field, and re-picking fields in the daemon would silently drop it (mutation-checked in `test/study-daemon.test.ts`). `STUDY_TOOLS` keeps the four tools' arguments and results out of memory capture. An implementer child gets `hub_study_show` and `hub_study_handoff` only. `Tasks.studyList` returns one page `{ campaigns, next? }` of receipts (ids, revision, status, counts), never question, scope or sources, at most 50 per call: the bound is the `LIMIT` in `Board.researchCampaignPage` (observed on its own SQL in `test/research-campaign-projection.test.ts`), `after` is the cursor, and a caller cannot raise the page size. `studyShow` blanks source ids for a private campaign and carries `budgetNamed`, so a named budget keeps every resource row with `-` for an absent limit: do not add text to either. `liveTestRefusal` refuses every `study` form except `show`, so an agent shell cannot delegate a study write to the person through `--live-test`; the contract file is read through one opened regular-file descriptor, at most 64 KiB plus one byte.
116
116
  - Folder triggers (#512c, `src/hub/folder-trigger.ts`, `Tasks.runFolderTriggers`): the folder pass shares `schedulesRunning` with the schedules pass and `ahub schedule run`, so it is serialized with them and counted by `schedulesPending`; keep it so, and never start it from a path that does not take the guard. A file is recorded in `folder_seen` only AFTER its task exists, and the pass stops at the first failure so order is kept; do not record first (a failure would lose the file) or continue past a failure. A task made for a file names the source id and never the file's name or path, because both can be private. A trigger's baseline (the files there when it starts, possibly over several polls) proposes nothing, and a refused folder never begins the trigger; do not make either propose. The scan must keep no file's bytes (it hashes in chunks under a file and byte budget and never cuts the listing); `registerScanned` re-reads the one file being registered and refuses it unless it still hashes to what the scan saw. Ask `isNew` for each file inside the loop, not only when building the list, and `observe` (never skip) every file whose content is already known, in the list and in the loop, so it is found by `known` next time and cannot starve a new file of the hash budget. `folder_triggers` stays in `MACHINE_LOCAL` and `NEVER_EDITABLE`.
package/docs/dogfood.md CHANGED
@@ -85,7 +85,7 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
85
85
  | Merge-time PR readback, release metrics and security-scan baseline (#481 S7, AC12) | source and fixture coverage with a fake gh and real train transitions; the readback is on wherever auto merge is `on`, release metrics need shipped trains with recorded history, and the security scan and its bands stay off here until `[security_scan]` and `[bands.security_open_alerts]` are set | `cost` on merge records, `ahub sdlc metrics` review/CI-at-merge rows, the releases row of `ahub sdlc release`, `security_scans` rows and `security_open_alerts` cards |
86
86
  | Required checks | advise (`check (ubuntu-latest)`, `check (macos-latest)`, `seeded guards`) | first-pass CI rate, same-head reruns (`hub_sdlc_costs`) |
87
87
  | Seed anchor diagnostics (#684) | on in the seeded checker; exact-match failure remains mandatory | missing anchors report the base commit, enclosing symbol and current file/line or explicit unknown; `test/seeded-check.test.ts` |
88
- | Release authorization (#481 S6) | pending (#199) | releases with a receipt |
88
+ | Release authorization (#481 S6, #1063) | fixture coverage; delegated live consent awaits release and project upgrade | `test/sdlc-release-authorization.test.ts` and flow fixtures retain exact candidate/docs/origin/restart bindings and record the actual user/conductor actor; agent-read and floor fixtures refuse other peers, private turns and live-test; no fixture authorizes a live release |
89
89
  | SDLC metrics and control bands (#481 S7) | `ahub sdlc metrics` on; bands off until `[bands]` is set in `.agenthub/sdlc.toml`, and they write cards only in advise or enforce (in off the report still shows tiers). Completion checks are off here, so `first_pass_check` and the PR-based figures read unknown | its own report; breach episodes per week by tier, and band cards the planner accepted rather than dropped |
90
90
  | Fresh headless reviewers (#223, #519) | configured here (`codex-review`); per-tool approval correction and no-verdict diagnostics/re-offer (#783) take effect after release and upgrade | reviews by fresh sessions, findings they add; a per-review start failure names a closed cause class (#722): `ahub status` reviewer line and hub.log `[cause: ...]`, with the share of failures that are `unknown`; stale admission skips and current-policy `no_verdict` reroutes leave the reviewer available (#781); a real `hub_review` verdict per verified backend/version, beyond inventory-only proof; bounded refusal diagnostics, confirmed re-offers without queue holds, and uncertain fallback holds |
91
91
  | Completion checks per class | off: the implementer already runs `scripts/check.sh`; a hub-run copy would double the heavy-slot use | time to verdict when on |
@@ -149,7 +149,9 @@ agent-hub is developed with agent-hub. This page lists every feature, whether it
149
149
  | Study CLI, tools and readback (#930 T3, #601) | Fake peers and a real daemon over its control socket, plus the real `ahub` process against that daemon; no native peer or live model has run a campaign here, so fake versus native evidence stays separate (the `research-live-example` claim in docs/profiles.md is still open) | `test/study-daemon.test.ts` (person's draft, approve and close; no agent reaches an approve or close op; planner drafts and cannot approve; read-only show; a private source shows no source id, question, scope or path; handoff only from the bound owner with every missing field named; the three-source, two-peer example moving from none to incomplete to complete), `test/output-cli.test.ts` (parse before connecting, `--json` byte for byte), `test/study-guidance.test.ts` (guidance names only published tools) |
150
150
  | Research dispatch and coverage (#930 T2) | Source/isolated fixtures only; no live research campaign | `test/research-dispatch.test.ts` and projection/recovery fixtures: no expired/revised dispatch, named-budget coverage, handoffs and shared ledger |
151
151
  | Experiment records (#932 T1) | Source and isolated fixtures only; no live research attempt | Schema, storage, Tasks and recovery fixtures: declared/host provenance stays distinct, all attempts counted, replay and privacy fences |
152
+ | Experiment record relay (task 615, #932) | Fixture only; available after the next release and upgrade, with no live experiment or runner started here | `test/research-experiment-record-daemon.test.ts` exercises authenticated owner/person relay, role and PII refusals, idempotent replay and inert payloads; capture/publication fixtures exclude raw receipt/signature data |
152
153
  | Experiment comparisons and protocol approval (#932 T2) | Fixture only; no scientific reproduction claimed on this repository | `test/research-comparison.test.ts`, `test/research-comparison-tasks.test.ts`, `test/research-protocol-cli.test.ts` and recovery tests verify incompatibility, fresh exact-hash approval, causal post-hoc context and restart identity; failed/cancelled attempts stay counted. |
154
+ | Experiment readback and receipt fixture (#932 T3, #602) | Fixture only: the readback (`ahub study experiments`, `hub_experiment_show`) is on after release and upgrade, but no live research attempt has been recorded here, and no native model or account has used it | `test/research-experiments-fixture.test.ts` (baseline, variant, failed and changed-protocol cases, a private input, a missing and a denied output, retry versus repeat; every value's source shown, every status counted, nothing run) and `test/study-guidance.test.ts` (who may call the read tool); the real use signal is `ahub study experiments` output on a campaign that has recorded attempts |
153
155
  | Registered runner receipts (#1018) | source and test-key fixtures only; no external runner or live scientific attempt has been admitted here | `test/research-runner-tasks.test.ts` covers signed binding, immutable replay, revocation/readback, privacy and schema-2 rollback; parser/projection fixtures cover missing fields and sealing negatives; config/recovery fixtures cover person-only trust roots, raw-key floors and integrity. `evidence.runnerAttested` stays separate from declared/host-measured values and reports current key status |
154
156
  | Dirty-submodule notices (#798) | source/fixture coverage; the live writing/general pilot (a workspace with 26 submodules) remains unverified | `ahub turns` shows `<path> (changed, contents unobserved)` for a turn that only edited a submodule; the D1 notice at `hub_task_propose` and `hub_task_accept`; `ahub undo` names what it cannot restore; a project without `.gitmodules` makes the same git calls per turn (counting-shim test) |
155
157
  | Profile scorecard (#799) | source/fixture coverage; live writing/general/research pilot remains unverified | ahub report --dogfood profile rows; pure and reader dev byte goldens, private-text exclusion, research declarations/unknown verdicts and per-source unknown tests; anonymous weekly receipt templates |
@@ -1,6 +1,6 @@
1
1
  # Operations guide
2
2
 
3
- This guide describes ahub 0.26.0 and control protocols 16 through 17. Live verification
3
+ This guide describes ahub 0.27.0 and control protocols 16 through 17. 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
@@ -1488,6 +1488,7 @@ ahub study approve rc-0123456789ab 2 # person only: create the unit task
1488
1488
  ahub study show [rc-0123456789ab] # list campaigns (50 per page, --after continues) or one projection
1489
1489
  ahub study close rc-0123456789ab 2 --reason "answered"
1490
1490
  ahub study approve-protocol <64-hex hash> # person only: approve one exact experiment protocol
1491
+ ahub study experiments --campaign rc-0123456789ab [--after 50] # read the recorded attempts (also --attempt ra-... or --plan <hash> --baseline ra-... --variant ra-...)
1491
1492
  ```
1492
1493
 
1493
1494
  - **Contract.** The JSON names the question, scope, exclusions, registered source ids, 1 to 32 units
@@ -1518,8 +1519,33 @@ ahub study approve-protocol <64-hex hash> # person only: approve one exact ex
1518
1519
  - **Experiments.** Protocols, comparison plans and attempts are stored with their provenance (`declared`,
1519
1520
  `host-observed` from a check receipt or a hub-hashed deliverable), and `ahub study approve-protocol` binds an
1520
1521
  approval to one exact protocol hash and its evaluator; a changed protocol needs its own approval. The hub
1521
- never ranks results. This release has no hub tool or CLI that records experiments; they are host storage
1522
- for the slices that follow.
1522
+ never ranks results. The current in-progress owner of a bound unit task records inert data through
1523
+ `hub_experiment_record`: `kind` is `protocol`, `plan`, `attempt`, `event` or `receipt`, using the existing
1524
+ protocol/attempt/event/plan envelope or `{ kind: "receipt", receipt, signature }` for a signed runner receipt.
1525
+ The person may relay a signed receipt through the authenticated console connection; this does not grant
1526
+ agents person authority or allow a person to bypass the current task/source/campaign checks. Every PII turn
1527
+ refuses the tool. It returns only record metadata (kind, ids/hashes, sequence and receipt id as applicable),
1528
+ and neither starts an experiment nor signs or verifies a receipt in the relay. Existing host evidence and
1529
+ signature verification remain inside `Tasks`. Read records with `ahub study experiments` or
1530
+ `hub_experiment_show` (see Experiment readback below).
1531
+ - **Experiment readback (#602, #932 T3).** `ahub study experiments --campaign <id>` lists a campaign's attempts
1532
+ (50 rows a page, `--after <cursor>` continues), `--attempt <ra-id>` shows one, and `--plan <hash> --baseline
1533
+ <ra-id> --variant <ra-id>` shows one comparison under a recorded plan. `hub_experiment_show` takes the same three
1534
+ forms and is a read for every local peer. It runs no command and reads no file; it renders what
1535
+ `Tasks.experimentShow` already returns. Every value says where it came from: `declared` (the agent's own word),
1536
+ `host-observed` (a check receipt or a hub-hashed deliverable), `runner-attested` (a signature-verified runner
1537
+ receipt) or `person-attested`. A declared value is never shown as observed, a metric or output the hub could not
1538
+ read is `unknown`, sealing is `declared` and `isolation unverified` unless a runner receipt supplies the
1539
+ evidence, and failed, cancelled and unknown attempts stay in the counts at the top. A repeat is never an
1540
+ independent reproduction, and a comparison whose fixed inputs, evaluator, split or metric units differ, or that
1541
+ changes an undeclared factor, reads `no: <field> differs` and is shown, never ranked. A private campaign, task or
1542
+ source shows ids, counts and states only. **Optional rerun checklist** (written for a person, never run by the
1543
+ hub): a person who wants stronger evidence reruns the protocol themselves, then attaches it through the existing
1544
+ paths and no new one: a check on the task (`hub_check_start`, #513 and the remote runner #793) makes the end
1545
+ event's exit status `host-observed`; an output file named in an `output` event is hashed by the hub (#509) and
1546
+ stays `unknown` when it is missing or denied; a registered runner's signed receipt (#1050) is the only source of
1547
+ `runner-attested` sealing and isolation. Without one of these the experiment is reported as declared and
1548
+ unverified, which is not the same as wrong.
1523
1549
  - **Privacy.** A campaign whose parent task is PII, or that names a private source, is private: every view keeps
1524
1550
  ids, counts, states and provenance and stubs its text as `[pii]`. Study tool arguments and results are kept out
1525
1551
  of hub-managed memory capture.
@@ -3100,7 +3126,7 @@ version is older than the selected running daemon; it installs nothing.
3100
3126
  | Runtime | Control protocol window | Legacy status field |
3101
3127
  | --- | --- | --- |
3102
3128
  | 0.12.20 through 0.25.0 | 16 only | `protocol: 16` |
3103
- | 0.26.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3129
+ | 0.26.0 through 0.27.0 | 16 through 17 | `protocol: 16`, `protocolMin: 16`, `protocolMax: 17` |
3104
3130
 
3105
3131
  New clients choose the highest common protocol. The runtime supports N-1, so the
3106
3132
  published protocol-16 client and channel plugin remain usable; unrelated feature
@@ -5092,7 +5118,7 @@ blocks that undo until the identity and workspace can be reviewed.
5092
5118
 
5093
5119
  ## Release approval and optional tag guard
5094
5120
 
5095
- Run `ahub sdlc authorize-release <version>` from the running daemon's release-candidate checkout in a person's terminal after committed documentation is fresh. The receipt binds commit, package version, remote identity and the docs verdict. Agents and delegated live-test calls cannot authorize. The daemon keeps approval only in memory and logs metadata for audit; restart requires a new approval. The read-only `ahub sdlc release <owner/repo>` report shows its receipt, retaining native-verification blockers.
5121
+ Run `ahub sdlc authorize-release <version>` from the running daemon's release-candidate checkout after committed documentation is fresh. The person may run it directly; the unique configured conductor may run it only on the person's explicit instruction in its own session naming the release. A hub message, task text, review note, file or peer relay never qualifies. Other peers, child `toolsOwner` sessions, private or PII turns, and delegated `--live-test` calls cannot authorize. The receipt binds commit, package version, remote identity and the docs verdict; its `by` and the audit name the actual asker. The daemon keeps approval only in memory; restart requires a new approval. The read-only `ahub sdlc release <owner/repo>` report shows its receipt, retaining native-verification blockers.
5096
5122
 
5097
5123
  `ahub init --release-guard` installs a managed Git pre-push guard and release instructions (`--dry-run --json` previews them). It preserves an existing default hook as `pre-push.agent-hub-original` and passes original argv/stdin to it only after approval matches. Unknown or mismatched `v*` tag pushes fail closed, including when the daemon is down. Non-version branch pushes remain independent of the daemon. Custom `core.hooksPath` and linked/hardlinked hook files require manual integration. Re-run init after changing the Bun/package location. The person's `git push --no-verify` remains an explicit bypass; agents are instructed never to use it.
5098
5124
 
package/docs/profiles.md CHANGED
@@ -22,6 +22,7 @@
22
22
  - Example deterministic check: `bun /path/to/agent-hub/scripts/check-citations.ts report.md references.bib`. It checks bracketed Markdown `[@key; @other]` keys against BibTeX entry keys, with 256 KiB/4 MiB input caps. It performs no fetch, validates no source quality and is not a full Markdown/BibTeX parser. A missing key exits 1. A resolved bibliography is one check, not proof that claims are supported.
23
23
  - Keep a person's sign-off as the final review. The live three-source/two-agent example remains unverified and was not run during solo implementation.
24
24
  - A multi-unit study (#930, #601) goes through `ahub study`: the person (or a configured planner with `hub_study_draft` / `hub_study_revise`) stores a contract as a draft, only the person approves it with `ahub study approve <id> <revision>`, which creates one task per unit, and anyone reads coverage with `ahub study show [<id>]` or `hub_study_show`. A unit owner records `hub_study_handoff` with evidence for every source the unit names, or `insufficientEvidence`; a done task is not a handoff, and an incomplete handoff does not close a discovery round. A private campaign, task or source shows ids, counts and states only.
25
+ - Experiment attempts recorded on a study unit are read with `ahub study experiments --campaign <id>` (or `--attempt`, or `--plan` with `--baseline` and `--variant`) and `hub_experiment_show`. Each value shows its source: `declared` is the agent's own word, and only `host-observed`, `runner-attested` or `person-attested` rows are evidence, none of which proves the science. A rerun by a person attaches through an existing check, a hashed output file or a registered runner's signed receipt, as the checklist in `docs/operations.md` ("Experiment readback") says; without one the experiment stays declared and its sealing unverified. The synthetic CPU-only fixture is `test/research-experiments-fixture.test.ts`; no account, GPU or native model has run an experiment here.
25
26
  - The deterministic three-source, two-peer example (`test/study-daemon.test.ts`, "three-source, two-peer example"): one contract names sources S0, S1 and S2 and two discovery units, `alpha` (S0 and S1, owned by codex) and `beta` (S2, an independent check, owned by kimi). After `ahub study approve` both units read `none` and `0/2 covered`, `0/1 covered`. A handoff for S0 only moves `alpha` to `incomplete` with `1/2 covered`. A handoff for both S0 and S1, and one for S2, make both `complete` and the discovery round complete. Another peer's handoff for `alpha` is refused. This runs with fake peers: it shows the hub's rules, not that a native model follows the guidance.
26
27
 
27
28
  # General project example
@@ -17,7 +17,7 @@ ahub setup # installs the channel plugin and a pr
17
17
  Or use the matching GitHub release:
18
18
 
19
19
  ```bash
20
- bun add -g github:STAIxBWLB/agent-hub#v0.26.0
20
+ bun add -g github:STAIxBWLB/agent-hub#v0.27.0
21
21
  ahub setup
22
22
  ```
23
23
 
@@ -31,6 +31,10 @@ move a running project to a newer release later, run `ahub upgrade` in a
31
31
  terminal in that project: it reviews the plan with you, applies it and follows
32
32
  it; other projects keep their version.
33
33
 
34
+ ## Upgrading to 0.27.0
35
+
36
+ Nothing changes until you use something new. Release consent can now come from your explicit instruction to the conductor: say in its own session which release to ship (for example "release 0.27.0"), and the configured conductor runs `ahub sdlc authorize-release <version>` for you, under the same admission as `sdlc merge`; you can still run it yourself. The approval keeps every binding it had (exact commit, version, origin and fresh docs; a restart revokes it), its record names who ran it, and a hub message, task text, review note, file or another peer's relay never counts as the instruction (#1063, #1066). Research experiments gain a read and a write: `ahub study experiments` and `hub_experiment_show` read a campaign's attempts, one attempt or one comparison (#602), and an owner records protocols, plans, attempts, events and receipts with `hub_experiment_record`, refused during a private turn (#615). Concurrent starts of one project no longer fail on a `runtime.json` replaced while it was being read (#616). As for 0.26.0, update the global CLI and the plugin yourself afterwards: `bun add -g @staix/agent-hub@0.27.0`, then `ahub setup`.
37
+
34
38
  ## Upgrading to 0.26.0
35
39
 
36
40
  `ahub upgrade` now upgrades one project: the one in the current directory, or the one `--project <path|id>` names; other projects keep their running version (#1039, #1045). From a 0.25.0 CLI, run it in the project directory, since 0.25.0 refuses `--project` for upgrade; it hands over to the 0.26.0 coordinator as before. It no longer updates the global CLI or the shared Claude plugin: run `bun add -g @staix/agent-hub@0.26.0` and then `ahub setup` yourself, and `ahub doctor` warns while either is older than a running daemon. The daemon speaks control protocol 17 and still accepts 16, so a 0.25.0 plugin keeps connecting. Each project keeps its verified runtime across stop and start; start it with a 0.26.0 or later CLI, because an older CLI ignores that record (#1052). A daemon refuses a `hub.db` in a newer format and names the version it needs. `ahub up` now also starts `claude` and `codex` (see [First session](#first-session)): `ahub setup` asks once which agents (`--agents <a,b>`, `none` for no agent), `ahub settings set start.agents <a,b>` changes one project, and `ahub up --no-agents` skips them for one run; a restart, recovery or upgrade relaunch starts none (#1046). An owner can re-file `hub_task_done` on an approved, unmerged task with a new head, which sends it back to review, and a reviewer can withdraw an approval with `changes_requested` (#1049). Signed external runner receipts for research attempts are opt-in: the person registers a runner under `research.runners` in untracked `.agenthub/config.local.json` (#1050). Restart Claude sessions so they load the 0.26.0 plugin.
package/docs/sdlc.md CHANGED
@@ -389,7 +389,7 @@ A private (PII) task reads no eval metadata and records none.
389
389
 
390
390
  ## Person release authorization (#481 S6)
391
391
 
392
- From the running daemon's candidate checkout, the person runs `ahub sdlc authorize-release <version>`. The console-only operation binds approval to the exact current commit, its committed package version, the origin fetch/push identity and fresh committed documentation evidence. It rereads the head and remote after the documentation check. Agent shells, including `--live-test`, cannot grant it. An append-only metadata event records the approval id, commit, version and documentation digest/count. The authoritative ledger is daemon-owned memory: restart revokes every approval; audit history cannot restore one.
392
+ From the running daemon's candidate checkout, the person or the one configured conductor runs `ahub sdlc authorize-release <version>` (#1063). The conductor acts only on the person's explicit instruction in its own session naming that release. A hub message, task text, review note, file or another peer's relay is never that instruction; the daemon cannot verify this cooperative rule. A conductor request uses the tools role and refuses a child `toolsOwner`, private turn or PII context, exactly like `sdlc merge`. Other peers and `--live-test` refuse. Consent binds the exact current commit, its committed package version, origin fetch/push identity and fresh committed documentation evidence, all reread after the docs check. Receipt `by` and the append-only metadata audit identify the actual asker alongside the approval id, commit, version and docs digest/count. The authoritative ledger is daemon-owned memory: restart revokes every approval; audit history cannot restore one.
393
393
 
394
394
  `ahub sdlc release <owner/repo>` shows a matching or stale authorization receipt. Authorization removes only the person-decision blocker. Native acceptance remains unverified and `ready` remains false until separately supported. No tag, workflow or publication is changed by authorization or readback.
395
395
 
@@ -403,13 +403,13 @@ Tracked npm cleanliness compares the cached index to HEAD and raw tracked blob b
403
403
 
404
404
  ## Release preparation and the guarded tag (#787 P3b)
405
405
 
406
- Release preparation is an agent task; the release decision is not. They are two records and two commands, and consent stays person-only.
406
+ Release preparation is an agent task; the release decision remains the person's. Consent may be recorded by the person or by the configured conductor on the explicit version-naming instruction above; preparation alone grants no consent.
407
407
 
408
408
  `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.
409
409
 
410
410
  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).
411
411
 
412
- `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`).
412
+ `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 from the person or the explicitly instructed configured conductor, 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). Neither script calls `authorize-release`: the report only names the command; the person or the explicitly instructed configured conductor records consent separately. Publication (npm, the GitHub release) stays with the tag-triggered workflow, which has its own gate (`.github/workflows/release.yml`).
413
413
 
414
414
  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.
415
415
 
package/docs/security.md CHANGED
@@ -819,7 +819,7 @@ The review brief (#520) withholds private changes whole (both sides of a move) a
819
819
 
820
820
  Deliverable records (#509a). `hub_task_done` and `hub_sdlc_record` name files as `{path, format, version?}`; the hub hashes them itself and refuses a claimed `sha256`, `hash` or `bytes` and any field it does not own. Paths resolve under the owner's execution root with `realPath` and `guardPath` in read mode: outside the project, the secrets denylist, the hub's own state (`.git`, `.agenthub`), a dangling link, a non-regular file and more than 256 MiB each or 512 MiB together are refused, with the path as given and never a resolved one. The file is opened without following a final link and its device, inode, size and time are compared with what was checked, before and after the read, so a swapped or changing file is refused rather than recorded half-read; a bad call writes nothing. A declared worktree instance whose root is not proven refuses, and a later read of it reports `unknown`. No hash of hub state is ever taken, and a file the hub cannot read or vouch for is `unknown`, never `unchanged`. A reviewer's approval re-hashes the files and is refused when one changed, went missing or is unknown; only the person may approve the current bytes, which are then measured and bound. For a PII task, a file under `pii.paths` or a path matching a PII pattern, `publicDeliverables` is the one projection every peer view, review request, status and log uses: id, hash and size only. The board keeps the path, as it keeps a PII task's text. `ahub deliverables` (#509b) from an agent shell lists only the tasks `hub_task_show` would show that agent (#933), and `--live-test` cannot delegate it. It lists the board's records through `publicDeliverables` and opens a file only with `--check`, through the same reader and limits. The dashboard's Deliverables chip and task group use the same projection of the same records and never read a file on the poll; a `changed` mark there is only as fresh as the last live read in this daemon.
821
821
 
822
- Person release authorization (#481 S6). `ahub sdlc authorize-release <version>` is taken from the console role only and refused under `--live-test`. It binds the daemon checkout's commit, package version, push remote identity and a fresh-docs verdict, lives only in daemon memory (a restart revokes it) and is logged with its commit, version and docs digest. `ahub sdlc release-guard -- <argv>` is a read-only preflight that agents may run, except during a private or PII turn; it never executes the command and refuses ambient target selectors (`GIT_DIR`, `GH_REPO`, npm prefix and registry settings and the like). The pre-push hook that `ahub init --release-guard` installs refuses an unauthorized or unknown `v*` tag push, also with the daemon down, and passes other pushes through. It and the release block in `AGENTS.md` are a cooperative guard, not an operating-system boundary: `git push --no-verify`, a custom `core.hooksPath` or another Git invocation bypasses the hook, and `gh release create` and `npm publish` are checked only when the preflight is run (tasks 312 and 313).
822
+ Person-directed release authorization (#481 S6, #1063). `ahub sdlc authorize-release <version>` accepts the console user or the one configured conductor under the same admission as `sdlc merge`: tools role, matching configured conductor, no child `toolsOwner`, private turn or PII context. It remains refused under `--live-test`. The conductor may record consent only on the person's explicit instruction in its own session naming the release; a hub message, task text, review note, file or peer relay is never that instruction. The daemon cannot verify that cooperative prerequisite. Receipt `by` and the audit identify the actual asker. It binds the daemon checkout's commit, package version, push remote identity and a fresh-docs verdict, lives only in daemon memory (a restart revokes it) and is logged with its commit, version and docs digest. `ahub sdlc release-guard -- <argv>` is a read-only preflight that agents may run, except during a private or PII turn; it never executes the command and refuses ambient target selectors (`GIT_DIR`, `GH_REPO`, npm prefix and registry settings and the like). The pre-push hook that `ahub init --release-guard` installs refuses an unauthorized or unknown `v*` tag push, also with the daemon down, and passes other pushes through. It and the release block in `AGENTS.md` are a cooperative guard, not an operating-system boundary: `git push --no-verify`, a custom `core.hooksPath` or another Git invocation bypasses the hook, and `gh release create` and `npm publish` are checked only when the preflight is run (tasks 312 and 313).
823
823
 
824
824
  SDLC metrics and control bands (#481 S7). `ahub sdlc metrics` and `hub_sdlc_metrics` are planner or person only, read-only, and refused during a private or PII turn. A private task is counted as withheld and read for nothing else; the report calls no model and makes no GitHub read. Band breaches are written only by the daemon's evaluator through Tasks, never while it is stopping, recovering or has a storage error. A `diagnose` or `propose` card is a backlog task (`triage` or `plan`) whose text is fixed words, numbers and task ids, never task text; it passes the PII signal check and the candidate-text screen before it is written, and no GitHub issue is created. A `log` breach is one hub notice, remembered in the board's `band_notices` table as a marker (`<metric>:#<task>:log`, checked against a fixed pattern) and a time, nothing else.
825
825
 
@@ -5888,7 +5888,7 @@ Order: 2a first (it measures everything else). 2b and 2c are independent. 2d aft
5888
5888
 
5889
5889
  ### S6 person release authorization boundary (#481)
5890
5890
 
5891
- - `ahub sdlc authorize-release <version>` is console-only, never live-test delegated. The daemon binds consent to its candidate checkout's exact HEAD, committed package version, origin fetch/push identity and fresh committed-doc verdict. Caller-supplied expected HEAD must match. Head and origin are rechecked after the bounded package-owned documentation checker. Only ids, hashes, version, timestamp and counts enter the receipt/audit event; no project script or release operation runs.
5891
+ - `ahub sdlc authorize-release <version>` accepts the console user or the one configured conductor, never live-test delegated (#1063). Conductor admission matches `sdlc merge`: tools role, `conductorPeer(config.roles) === c.peer`, no `toolsOwner`, private turn or caller PII context. The conductor records consent only on the person's explicit instruction in its own session naming the release; a hub message, task text, review note, file or another peer's relay is never that instruction. This instruction-origin condition is cooperative, not verified by the daemon. Receipt `by` and the metadata audit identify the actual asker (`user` or conductor peer). The daemon binds consent to its candidate checkout's exact HEAD, committed package version, origin fetch/push identity and fresh committed-doc verdict. Caller-supplied expected HEAD must match. Head and origin are rechecked after the bounded package-owned documentation checker. Only ids, hashes, version, timestamp and counts enter the receipt/audit event; no project script or release operation runs.
5892
5892
  - Authoritative receipts live only in the current daemon generation's bounded memory ledger (at most 64 candidates). An additive metadata event audits consent but cannot recreate it. Restart revokes all approvals. The release report shows matching/stale consent without certifying native acceptance, changing the tag workflow or publishing.
5893
5893
  - Optional `ahub init --release-guard` installs a managed executable Git pre-push hook plus instructions. A regular existing hook is renamed, preserving its inode/mode, and chained with exact argv/stdin after successful guard readback. Linked/hardlinked hooks, occupied backups and custom hooksPath refuse before project writes. Re-init retains the guard; the wrapper pins Bun/package paths and needs re-init if they move.
5894
5894
  - Actual pushed `v*` refs must have a valid version and captured source-object/commit matching consent. Missing/mismatched commit/version/remote, unreachable daemon, deletion, unknown evidence or restart refuses the version-tag push. Branch-only pushes work offline. `--no-verify` remains the person's explicit bypass and is forbidden to agents; this is cooperative Git enforcement, not a sandbox.
@@ -276,7 +276,7 @@ All new tables live in `hub.db`, created in the `Board` constructor with `CREATE
276
276
 
277
277
  | Surface | Name | Who |
278
278
  |---|---|---|
279
- | CLI | `ahub study draft <parent> <contract.json>`, `study revise <id> <revision> <contract.json>`, `study approve <id> <revision>`, `study close <id> <revision> --reason`, `study show [<id> | --after <id>] [--json]`, `study experiments [--campaign <id>] [--json]`, `study attest <attempt> <field> <value>`, `study receipt <file>` | approve, attest and receipt are person only; draft and revise are person or planner; show is anyone |
279
+ | CLI | `ahub study draft <parent> <contract.json>`, `study revise <id> <revision> <contract.json>`, `study approve <id> <revision>`, `study close <id> <revision> --reason`, `study show [<id> | --after <id>] [--json]`, `study experiments (--campaign <id> [--after <n>] | --attempt <ra-id> | --plan <hash> --baseline <ra-id> --variant <ra-id>) [--json]`, `study attest <attempt> <field> <value>`, `study receipt <file>` | approve, attest and receipt are person only; draft and revise are person or planner; show and experiments are anyone. `experiments` ships in #602 in exactly those three closed forms; `attest` and `receipt` are not built yet (see the #602 row) |
280
280
  | Hub tools | `hub_study_draft` and `hub_study_revise` (configured planner only, through `planningAuthority`; they store drafts and never approve), `hub_study_show` (read-only; no id lists this project's campaigns as receipts, one page of at most 50 as `{ campaigns, next? }`, and `after` continues from the `next` of the previous page), `hub_study_handoff` (bound task owner), `hub_experiment_record` (`kind`: `protocol`, `plan`, `attempt`, `event` or `receipt`; task owner), `hub_experiment_show` (read-only) | none of them approves or attests |
281
281
  | CLI (person only) | `ahub study approve-protocol <hash>` | person |
282
282
  | Control | `study` (console role, CLI only) | person |
@@ -299,6 +299,17 @@ Each slice amends this spec in its own PR if it must differ, and updates `docs/d
299
299
  | #601 | #930 T3, AC5 and AC6 | `ahub study` draft, revise, approve, close and show; `hub_study_draft`, `hub_study_revise`, `hub_study_show` and `hub_study_handoff`; the privacy stubs; role guidance in `templates/`; the three-source, two-peer example |
300
300
  | #602 | #932 T3, AC5 and AC6 | `ahub study experiments`, `attest` and `receipt`; `hub_experiment_show`; the synthetic CPU-only fixture; the rerun checklist in `docs/profiles.md` |
301
301
 
302
+ **As built in the #602 PR (amended here, because the build differs from this row).** Built: `ahub study experiments` in its three closed forms, the read-only `hub_experiment_show` (a read for every local peer, in `STUDY_TOOLS`, the read-only tool lists and `SESSION_READ_OPS`), the synthetic CPU-only fixture `test/research-experiments-fixture.test.ts`, and the person-authored rerun checklist, which lives in `docs/operations.md` ("Experiment readback") with a pointer from `docs/profiles.md`. Not built, and not needed for AC5 or AC6: `ahub study attest`, `ahub study receipt` and the `hub_experiment_record` tool. `Tasks.experimentRecord` and its receipt kind exist and are tested, but no tool, daemon case or CLI calls them yet, so until that slice lands the readback shows only what a test or a later slice recorded. Each of those writers needs its own authority review (person-only attest and receipt, task-owner record) and is a separate change.
303
+
304
+ **Task 615 record relay.** `hub_experiment_record` passes the whole inert envelope to
305
+ `Tasks.experimentRecord` with the authenticated caller identity. Its five existing kinds are `protocol`,
306
+ `plan`, `attempt`, `event` and `receipt`. Ordinary records require the current in-progress owner of a bound
307
+ unit in the current approved campaign; the person may relay a signed runner receipt through the existing
308
+ `USER` branch. This is not a new attestation or approval authority. All PII turns refuse before dispatch,
309
+ read-only callers cannot mutate, and arguments/results are excluded from hub-managed memory capture.
310
+ The relay returns the method's bounded record metadata only and performs no execution, signing or
311
+ verification itself. `ahub study attest` and `ahub study receipt` remain separate, unimplemented forms.
312
+
302
313
  ## Departures from the issues
303
314
 
304
315
  - Runner registration is person only in V1. #1018 proposed "the person or conductor". A public key is a trust root, so the conductor path is deferred until a reviewed change gives it the same confirmation as other trust settings.
@@ -0,0 +1,49 @@
1
+ # 0.27.0 release verification
2
+
3
+ This record covers release preparation and source review. It claims no installation, no running-hub upgrade to 0.27.0 and no live TUI launch.
4
+
5
+ ## Candidate
6
+
7
+ Version 0.27.0, prepared from main de08b8e1 (4 commits since v0.26.0, 3 of them `feat`; bump rule: minor). The release commits carry the version, the CHANGELOG section, the bundle and the documentation stamps.
8
+
9
+ ## Reviews
10
+
11
+ Pull requests named by the commits since v0.26.0: #1066, #1062, #1061, #1059.
12
+
13
+ Each merged after a claude `hub_review` approval with exact-head CI green, no unresolved review thread and no pending change request:
14
+
15
+ - #1059 (task 602, experiment readback): approved at c10a509f, exact-head CI run 41313240639. One nit (the spec's as-built note could name task 615) left open; #1062 then named it.
16
+ - #1061 (task 616, runtime.json read retried only when the file was replaced): approved after exact-head CI run 41318247416 at 636fe45f. The board bound the approval to 6c6dfeff, because hub 0.23.1 refuses an in_review re-file; the verdict note names the one-line CHANGELOG delta to 636fe45f.
17
+ - #1062 (task 615, `hub_experiment_record`): approved at 66b76a8e, run 41322472076.
18
+ - #1066 (task 617, conductor-recorded release consent, #1063): approved at 6f3b2dfc, run 41341091037. The first head 5f12dec3 failed CI on two assertions of wording #1063 changed (run 41339855105); the reviewer ruled that exactly those two be updated to the new text, and the delta to 6f3b2dfc is those two lines.
19
+
20
+ ## Native acceptance receipts
21
+
22
+ Receipts added under `docs/verification/` since v0.26.0: none.
23
+
24
+ This list is read from the repository, never written by hand. Native acceptance and the person's release consent are separate records: consent is `ahub sdlc authorize-release 0.27.0`, recorded by the person or the unique configured conductor only on the person's explicit release instruction naming the release in its own session (never a hub/task/review/file/peer relay), and is not recorded in this file. The tag run refuses without it.
25
+
26
+ ## Document review
27
+
28
+ Stale at preparation (`node scripts/check-docs.mjs`): README.md, docs/agent-notes/{adapters,budget,bus,daemon,tasks,tests}.md, docs/operations.md, docs/quickstart.md, docs/security.md and src/cli/help.ts; docs/agent-notes/README.md became stale after the daemon.md edit. The conductor read each against the diff of its covered sources since v0.26.0 (#1059, #1061, #1062, #1066) and found:
29
+
30
+ - src/cli/help.ts: `ahub study experiments` (three closed forms) shipped in #1059 without a help row; added. Every other form in `ahub help` still matches `src/cli/main.ts`, `src/cli/study.ts` and `src/cli/launch.ts`; the `sdlc authorize-release` row was already updated by #1066.
31
+ - docs/agent-notes/daemon.md: the `ahub study` note now names `experiments` as an allowed read form beside `show`.
32
+ - docs/quickstart.md: the v0.27.0 install tag and a new "Upgrading to 0.27.0" section (instructed release consent, experiment read and record, the runtime.json race fix, and updating the global CLI and plugin by hand as in 0.26.0).
33
+ - README.md: the report link, and one sentence on experiment record and readback under Research campaigns.
34
+ - docs/operations.md: the guide header (0.27.0) and the protocol-window table row (0.26.0 through 0.27.0, unchanged window 16 through 17). The experiment and release-approval sections were already updated by #1059, #1062 and #1066.
35
+ - docs/security.md, docs/agent-notes/{tasks,tests}.md: already current (#1066 updated the release-authorization paragraphs; tasks.md names both experiment tools). adapters.md, budget.md, bus.md and agent-notes/README.md: stale only through covered daemon.ts or note paths; nothing in them describes the changed behavior.
36
+
37
+ All twelve were restamped with `--reviewed` at the release branch head that holds these edits.
38
+
39
+ ## Checks
40
+
41
+ - `node scripts/check-docs.mjs --require-fresh`: `docs: OK (0 stale documents)` on the release branch.
42
+ - `bun test test/help.test.ts test/completion.test.ts` after the help row: 24 pass, 0 fail.
43
+ - `bun x tsc --noEmit`, `bun scripts/build.mjs --check` and `node scripts/check-package.mjs`: run by the release PR's exact-head CI, together with the full suite on ubuntu and macOS and the seeded guards (heavy checks run only on CI or dgx02 on this machine).
44
+
45
+ ## Known gaps, not blocking
46
+
47
+ - No installation and no running-hub upgrade to 0.27.0 is claimed here. The running hub on this machine is 0.23.1, so the conductor-recorded consent of #1063 is not live yet; this release's consent is the person's own `ahub sdlc authorize-release 0.27.0`.
48
+ - No live receipt for `hub_experiment_record` or the instructed-consent path; both are covered by daemon-level tests only.
49
+ - Issue #418 was closed with its managed-Pi live leg waived (owner decision 2026-11-02); a post-close Pi smoke on hub 0.23.1 (tasks 618, 619) passed spec read, memory read and one remote isolated check, Linux evidence only.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "documents": {
4
4
  "README.md": {
5
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
5
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
6
6
  "paths": [
7
7
  "package.json",
8
8
  "src/",
@@ -11,14 +11,14 @@
11
11
  ]
12
12
  },
13
13
  "docs/security.md": {
14
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
14
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
15
15
  "paths": [
16
16
  "src/",
17
17
  "templates/"
18
18
  ]
19
19
  },
20
20
  "docs/operations.md": {
21
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
21
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
22
22
  "paths": [
23
23
  "package.json",
24
24
  "src/",
@@ -35,7 +35,7 @@
35
35
  ]
36
36
  },
37
37
  "docs/quickstart.md": {
38
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
38
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
39
39
  "paths": [
40
40
  "package.json",
41
41
  "src/",
@@ -43,7 +43,7 @@
43
43
  ]
44
44
  },
45
45
  "docs/agent-notes/adapters.md": {
46
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
46
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
47
47
  "paths": [
48
48
  "src/adapters/",
49
49
  "src/pi/",
@@ -93,7 +93,7 @@
93
93
  ]
94
94
  },
95
95
  "docs/agent-notes/budget.md": {
96
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
96
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
97
97
  "paths": [
98
98
  "src/hub/budget.ts",
99
99
  "src/cli/statusline-tee.ts",
@@ -117,7 +117,7 @@
117
117
  ]
118
118
  },
119
119
  "docs/agent-notes/bus.md": {
120
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
120
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
121
121
  "paths": [
122
122
  "src/hub/bus.ts",
123
123
  "src/hub/envelope.ts",
@@ -132,7 +132,7 @@
132
132
  ]
133
133
  },
134
134
  "docs/agent-notes/daemon.md": {
135
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
135
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
136
136
  "paths": [
137
137
  "src/hub/daemon.ts",
138
138
  "src/hub/control-client.ts",
@@ -243,7 +243,7 @@
243
243
  ]
244
244
  },
245
245
  "docs/agent-notes/tasks.md": {
246
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
246
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
247
247
  "paths": [
248
248
  "src/hub/tasks.ts",
249
249
  "src/hub/board.ts",
@@ -291,7 +291,7 @@
291
291
  ]
292
292
  },
293
293
  "docs/agent-notes/tests.md": {
294
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
294
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
295
295
  "paths": [
296
296
  "test/",
297
297
  "scripts/check.sh",
@@ -320,7 +320,7 @@
320
320
  ]
321
321
  },
322
322
  "docs/agent-notes/README.md": {
323
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
323
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
324
324
  "paths": [
325
325
  "src/cli/console-state.ts",
326
326
  "src/cli/attention.ts",
@@ -329,7 +329,7 @@
329
329
  ]
330
330
  },
331
331
  "src/cli/help.ts": {
332
- "verifiedAgainst": "362859ec83ca659483423393174a294a5c40bb90",
332
+ "verifiedAgainst": "c3f379ce1ce1c4c1f7b5442c78fd8c534ff12ec0",
333
333
  "paths": [
334
334
  "src/cli/main.ts",
335
335
  "src/cli/launch.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staix/agent-hub",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Native multi-agent hub: Claude Code, Codex, Kimi Code, Pi and local inference as peers in one project",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-hub",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Channel between Claude Code and the agent-hub daemon: peer messages from Codex, Kimi and the local worker arrive as channel events; hub_send replies.",
5
5
  "author": {
6
6
  "name": "Young Joon Lee",