axstack 0.54.0 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/docs/concepts.md +3 -1
  2. package/docs/installation.md +5 -2
  3. package/docs/workflows.md +3 -1
  4. package/package.json +1 -1
  5. package/profiles/presets/claude-only.json +9 -0
  6. package/profiles/presets/codex-only.json +9 -0
  7. package/profiles/presets/mixed.json +9 -0
  8. package/skills/axstack/references/fleet-dispatch.md +166 -0
  9. package/skills/axstack/references/fleet-route-schema.md +325 -0
  10. package/skills/axstack/references/fleet-verification.md +102 -0
  11. package/skills/axstack/references/lifecycle.md +19 -2
  12. package/skills/axstack/references/role-roster.md +11 -0
  13. package/skills/axstack/references/routing.md +14 -0
  14. package/skills/axstack/references/run-record.md +83 -0
  15. package/skills/axstack/references/t3-runtime.md +174 -7
  16. package/skills/axstack/references/ui-verification.md +67 -4
  17. package/skills/axstack/references/workspace-hygiene.md +8 -0
  18. package/skills/axstack/scripts/dispatch-plan.js +52 -8
  19. package/skills/axstack/scripts/fleet-config.js +78 -0
  20. package/skills/axstack/scripts/fleet-destination.js +39 -0
  21. package/skills/axstack/scripts/fleet-dispatch.js +47 -0
  22. package/skills/axstack/scripts/fleet-facts.js +161 -0
  23. package/skills/axstack/scripts/fleet-inventory.js +76 -0
  24. package/skills/axstack/scripts/fleet-proc.js +31 -0
  25. package/skills/axstack/scripts/fleet-receipt.js +130 -0
  26. package/skills/axstack/scripts/fleet-route-cli.js +78 -0
  27. package/skills/axstack/scripts/fleet-route.js +86 -0
  28. package/skills/axstack/scripts/fleet-sample-cli.js +81 -0
  29. package/skills/axstack/scripts/fleet-sample-io.js +59 -0
  30. package/skills/axstack/scripts/fleet-sample-manifest.js +34 -0
  31. package/skills/axstack/scripts/fleet-sample.js +48 -0
  32. package/skills/axstack/scripts/resolve-models.js +8 -4
  33. package/skills/axstack-cleanup/SKILL.md +21 -0
  34. package/skills/axstack-implement/SKILL.md +12 -5
  35. package/skills/axstack-review/references/review-candidate.md +1 -1
  36. package/skills/axstack-verify/SKILL.md +17 -6
  37. package/skills/axstack-watch/SKILL.md +3 -0
  38. package/skills/axstack-watch/references/watch-runtime.md +18 -5
  39. package/skills/voa/SKILL.md +96 -0
  40. package/skills/voa/references/observations.md +79 -0
  41. package/skills/voa/references/recovery.md +94 -0
  42. package/skills/voa/references/verification.md +35 -0
  43. package/skills/voa/scripts/cleanup.js +39 -0
  44. package/skills/voa/scripts/evaluate.js +122 -0
  45. package/skills/voa/scripts/observations.js +68 -0
  46. package/skills/voa/scripts/records.js +48 -0
  47. package/skills/voa/scripts/voa.js +43 -0
  48. package/src/installer.js +3 -2
  49. package/src/instructions.js +11 -3
  50. package/src/roles.js +2 -2
package/docs/concepts.md CHANGED
@@ -59,7 +59,9 @@ do not belong in it.
59
59
 
60
60
  Delegation uses the `t3-code` MCP. Use async `delegate_task` for every role, including authors with
61
61
  `isolation:"worktree"` in their own SHA-pinned worktrees.
62
- Top-level drivers use `t3_thread_launch`.
62
+ Every top-level launch, including an author fallback, requires an explicit user request.
63
+ Top-level drivers use `t3_thread_launch` only after an explicit user request.
64
+ Preserve existing accepted authorization within its exact scope.
63
65
  Use `t3_thread_launch` as a rare author fallback only when
64
66
  worktree isolation is unavailable and record each use in the run record. Configuration read-back,
65
67
  run receipts and revision-bound completion are separate from capability
@@ -215,10 +215,13 @@ also routes every subagent, delegated worker, reviewer, and cross-harness dispat
215
215
  through the `t3-code` MCP. It forbids harness-native subagent tools. The current
216
216
  T3 thread is the driver. Use async `delegate_task` for every role, including authors with
217
217
  `isolation:"worktree"` in their own SHA-pinned worktrees.
218
- Top-level drivers use `t3_thread_launch`.
218
+ Every top-level launch, including an author fallback, requires an explicit user request.
219
+ Top-level drivers use `t3_thread_launch` only after an explicit user request.
220
+ Preserve existing accepted authorization within its exact scope.
219
221
  Use `t3_thread_launch` as a rare author fallback only when
220
222
  worktree isolation is unavailable and record each use in the run record. The standing block
221
- authorizes top-level driver threads, delegated writers and worktrees;
223
+ authorizes delegated writers and worktrees within approved scope;
224
+ it grants no blanket top-level launch permission;
222
225
  it grants no push, merge, release, or host mutation.
223
226
  The [runtime reference](../skills/axstack/references/t3-runtime.md) owns the dispatch
224
227
  and receipt protocol.
package/docs/workflows.md CHANGED
@@ -201,7 +201,9 @@ All subagent, delegated-worker, reviewer, and cross-harness work uses T3
201
201
  orchestration through the `t3-code` MCP. Do not use harness-native subagent
202
202
  tools. Use async `delegate_task` for every role, including authors with
203
203
  `isolation:"worktree"` in their own SHA-pinned worktrees.
204
- Top-level drivers use `t3_thread_launch`.
204
+ Every top-level launch, including an author fallback, requires an explicit user request.
205
+ Top-level drivers use `t3_thread_launch` only after an explicit user request.
206
+ Preserve existing accepted authorization within its exact scope.
205
207
  Use `t3_thread_launch` as a rare author fallback only when
206
208
  worktree isolation is unavailable and record each use in the run record.
207
209
  Reviewers and investigators receive driver-made disposable detached checkouts pinned to candidate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axstack",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "Axstack installer and setup CLI: installs owned chat skills and role data, configures supported harness settings, and checks T3 Code capabilities.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -163,6 +163,15 @@
163
163
  "notes": "Read-only UI verifier: uses the configured verifier browser tools against the given build, URL, or artifact; captures screenshots, interactions, accessibility, desktop/mobile, and reduced-motion evidence in the dispatch evidence folder; returns a verdict with evidence paths. Performs the inline page interaction pass for every interactive page, bound to the exact bytes and SHA-256. Never edits source. T3 resolves Claude classes from saved capabilities; a Claude rejection holds.",
164
164
  "modelClass": "sonnet"
165
165
  },
166
+ {
167
+ "id": "axstack-ui-verifier-sonnet",
168
+ "name": "Axstack UI verifier Sonnet",
169
+ "provider": "claude",
170
+ "model": null,
171
+ "modeId": "bypassPermissions",
172
+ "thinkingOptionId": "high",
173
+ "notes": "Intentional single-provider absence: the mixed Sonnet UI pair is unavailable in this preset. Record dual-run verification as unverified without substitution."
174
+ },
166
175
  {
167
176
  "id": "axstack-explore-codebase",
168
177
  "name": "Axstack codebase explorer",
@@ -163,6 +163,15 @@
163
163
  "notes": "Read-only UI verifier: uses the configured verifier browser tools against the given build, URL, or artifact; captures screenshots, interactions, accessibility, desktop/mobile, and reduced-motion evidence in the dispatch evidence folder; returns a verdict with evidence paths. Performs the inline page interaction pass for every interactive page, bound to the exact bytes and SHA-256. Never edits source. Resolve the class from saved T3 capabilities at run start; rejection and unavailable settings hold without substitution.",
164
164
  "modelClass": "sol"
165
165
  },
166
+ {
167
+ "id": "axstack-ui-verifier-sonnet",
168
+ "name": "Axstack UI verifier Sonnet",
169
+ "provider": "codex",
170
+ "model": null,
171
+ "modeId": "full-access",
172
+ "thinkingOptionId": "high",
173
+ "notes": "Intentional single-provider absence: the mixed Sonnet UI pair is unavailable in this preset. Record dual-run verification as unverified without substitution."
174
+ },
166
175
  {
167
176
  "id": "axstack-explore-codebase",
168
177
  "name": "Axstack codebase explorer",
@@ -164,6 +164,15 @@
164
164
  "notes": "Read-only UI verifier: uses the configured verifier browser tools against the given build, URL, or artifact; captures screenshots, interactions, accessibility, desktop/mobile, and reduced-motion evidence in the dispatch evidence folder; returns a verdict with evidence paths. Performs the inline page interaction pass for every interactive page, bound to the exact bytes and SHA-256. Never edits source. Resolve the Terra class from saved T3 capabilities at run start; rejection and unavailable settings hold without substitution.",
165
165
  "modelClass": "terra"
166
166
  },
167
+ {
168
+ "id": "axstack-ui-verifier-sonnet",
169
+ "name": "Axstack UI verifier Sonnet",
170
+ "provider": "claude",
171
+ "modeId": "bypassPermissions",
172
+ "thinkingOptionId": "high",
173
+ "notes": "Read-only independent run 2 verifier for Android and web/PWA: same brief and pinned build, never sees run 1 results. Uses T3 device tools and Playwright MCP under ui-verification.md; returns per-feature evidence in its private folder. Never edits source. T3 resolves Claude classes from saved capabilities; a Claude rejection holds. Missing Sonnet high holds without substitution.",
174
+ "modelClass": "sonnet"
175
+ },
167
176
  {
168
177
  "id": "axstack-explore-codebase",
169
178
  "name": "Axstack codebase explorer",
@@ -0,0 +1,166 @@
1
+ # Fleet nonwriter dispatch and receipts (C3a)
2
+
3
+ This contract joins C1 recommendation and C2 sampling to native T3 execution.
4
+ It does not deploy a routing switch. Read [T3 runtime](t3-runtime.md) and the
5
+ [selector schema](fleet-route-schema.md). The driver owns all native calls,
6
+ records and forge actions. Helpers validate explicit saved inputs only: no
7
+ network, clock, dispatch, retry, merge, retained state or aggregate permission grant.
8
+ Synthetic evidence can pass binding checks; helpers cannot authenticate it.
9
+
10
+ ## Observe before selecting
11
+
12
+ 1. Record installed Axstack and both T3 versions, directed origin/target link,
13
+ explicit time, exact reviewed config bytes/digest and installed role snapshot.
14
+ Map each environmentId to its configured hostId. Hosts, environments, projects,
15
+ threads and paths are distinct namespaces. Never reuse origin project/thread IDs
16
+ or filesystem paths on a destination by assumption.
17
+ 2. On origin call `t3_environment_links`; preserve raw output and expiry/access.
18
+ On each eligible destination use forwarded `t3_environment_read`,
19
+ `t3_project_list` (all pages), `t3_project_read` for the actual project and
20
+ `orchestrator_capabilities({environmentId})`. Preserve exact catalog bytes and
21
+ SHA-256. Observe repository identity/workspaceRoot and actual required session
22
+ access there. Authentication occurs on that session host; copy no credentials.
23
+ Discovery is not proof that the provider executes. Unknown catalog, project,
24
+ mode, options, access, link or required session excludes that destination.
25
+ 3. Keep the model/effort/provider pins from the run; do not re-resolve to another
26
+ model when quota/auth/timeout fails. Missing native `t3_thread_configuration`
27
+ means unavailable effective fields stay unknown. Catalog runtimeModes advertise
28
+ support; they do not prove effective access. Native task/thread facts supply
29
+ only the fields actually returned. A worker assertion is not native readback.
30
+ 4. Normalize current driver assignments and the pending batch as below. Sample
31
+ telemetry with C2's bounded command, then recompute C1 immediately before each
32
+ dispatch using the collector time and current exclusions. Preserve the full
33
+ recommendation JSON bytes and complete replayable input. Unknown inventory
34
+ is unknown, not a claimed zero; pending lower bounds are labelled separately.
35
+
36
+ ### Saved native inventory
37
+
38
+ Set optional selector input `nativeInventory` to
39
+ `{complete, observedAt, evidence, tasks, pending}`. With this field, it is the
40
+ inventory source; legacy normalized arrays remain for non-fleet callers.
41
+ `complete:true` requires the driver's complete current assignment collection,
42
+ including unsettled attempts, descendants and pending submissions. API failures,
43
+ pagination gaps, missing ownership or unreconciled launches set complete false.
44
+ The helper cannot independently prove that a caller collected every task.
45
+
46
+ Each tasks entry is `{origin:{environmentId,threadId}, clientRequestId, task,
47
+ assignmentRoot?}`. `task` is saved native task_status, with its actual taskId.
48
+ For remote tasks use remoteChild.environmentId/threadId even if childThreadId
49
+ is null. For local tasks require a nonempty childThreadId before using origin's
50
+ environment. Native childRunId is an initial observation, not a final-run pin.
51
+ Origin-qualified JSON tuple task identities avoid cross-host ID collisions.
52
+ Do not count an unrelated thread just because its ID matches on another host.
53
+
54
+ A nested entry can name `assignmentRoot:{environmentId,taskId}` for its observed
55
+ coordinator task. Both must exist on the same destination; unresolved/conflicting
56
+ roots remain unknown. One assignment counts once while any member is active.
57
+ This normalization supports evidence reconciliation; it does not enable authors.
58
+ Pending entries are `{origin:{environmentId,threadId},clientRequestId,environmentId,
59
+ taskId?}`. Update a pending entry with its returned taskId; accepted/nested/terminal
60
+ reflections count once. Ambiguous submissions remain pending until reconciled;
61
+ remove only confirmed not-submitted failures. Client identity includes the driver
62
+ thread, not just its host. Unknown native inventory emits null totals and explicit
63
+ pendingLowerBound/reasons/evidence. Balance ranking uses that pending lower bound
64
+ while reported totals remain null. Legacy C1 fixtures and 20/35/40 spread remain.
65
+
66
+ ## Bind and submit once
67
+
68
+ For live use supply `input.nativeDestination` before dispatch-plan binding. Each
69
+ read envelope is `{tool,environmentId,args,observedAt,evidence,value}` where args
70
+ excludes the forwarding environmentId, and evidence points to the preserved raw
71
+ native call/result. environment/project/capabilities are read on the selected
72
+ host; links are read on origin. The capabilities envelope uses `valueText` for
73
+ exact raw catalog bytes instead of value. Project repositoryIdentity.canonicalKey
74
+ must match the requested repositoryId. Unknown mappings hold rather than guessing.
75
+ The pure `inspectDestination` API returns per-term pass/fail/unknown with evidence;
76
+ `dispatch-plan` holds on a failed/unknown term. This is supplied binding proof only.
77
+
78
+ Run existing dispatch-plan with the selected destination catalog, complete input,
79
+ recommendation, explicit evaluation time and config pin. Compare its target to the
80
+ independently selected host/environment/project/model/options/runtimeMode. Copy only
81
+ native supported fields, never inputs or fleetBinding. Call `delegate_task` with
82
+ that exact target, full-access, a role-labelled title, stable clientRequestId and
83
+ scope. Reviewers require native isolation:"worktree" and pinned candidate/base;
84
+ no shell-created child checkout. Other nonwriters write only their named private
85
+ evidence directory on that host. Preserve requested arguments and native response.
86
+
87
+ Record taskId on the origin plus remoteChild.environmentId/threadId. The native
88
+ remoteChild schema need not return runId: normalize it from observed task/thread
89
+ run facts, preserving the native raw response separately. Do not invent it.
90
+ Never top-level launch unless the user explicitly requested a new thread.
91
+ No agent execution through SSH; T3 forwarding is the only remote execution route.
92
+ Unknown submission/liveness blocks replacement. Reconcile the exact attempt and
93
+ clientRequestId against the native remote task before retrying; do not launch a
94
+ second task to test absence. Follow native next instructions, without polling.
95
+ Confirmed pre-submission rejection may exclude a route and trigger a new current
96
+ recommendation at the same model/permission pins; record the exclusion evidence.
97
+
98
+ ## Terminal receipt
99
+
100
+ Preserve schemaVersion:1, recommendationJSON (exact bytes), configDigest,
101
+ attempt (key, exact title, clientRequestId, origin environmentId/threadId/taskId,
102
+ candidate/base SHAs), requested target/runtimeMode/isolation and native submission.
103
+ The driver's independent fleetAttempt is the expected run-record identity;
104
+ never copy it from an arriving receipt to make a comparison pass.
105
+
106
+ After native completion wakes origin, read task_status there first. Read actual
107
+ destination thread and terminal run with forwarded t3_thread_read/t3_thread_wait,
108
+ then `t3_pending_request` action list for that same thread. Save envelopes status,
109
+ thread, terminal and questions using the read format above. Preserve raw native
110
+ question output; questions.value.requests is a normalized list extracted from that
111
+ observed response, not an invented API schema. If extraction is uncertain, omit
112
+ it and keep unknown. The native tool currently advertises an opaque result shape.
113
+
114
+ Normalize remoteChild:{environmentId,threadId,runId} to the current destination run.
115
+ Task latestTerminalRunId, thread latestRunId/recentRuns ordinal, terminal runId
116
+ (and resultRun when present) and final destination message must identify that
117
+ same current completed run. Provider continuation may have a different run from
118
+ submission.childRunId; never use lexical ordering of IDs or the original summary.
119
+ Require completed/result_available/no pending children/no unanswered questions or
120
+ approval. Require exactly one AXSTACK-DONE key/candidate/report marker on both origin's
121
+ latestTerminalSummary and the destination's final current-run agent message.
122
+ A completion marker may be followed by the T3
123
+ `PR <url>, head <sha>, checks and reviews <state>` trailer. Other trailing text
124
+ or additional AXSTACK markers keep the receipt held.
125
+ Old summary, null local child, idle, silence and completed turns with live children
126
+ remain incomplete. Failed/cancelled/interrupted/waiting never succeed.
127
+
128
+ Evidence is `{hostId,environmentId,path}` with an absolute destination path.
129
+ For isolated review include checkout with those fields, candidate/base and readback:
130
+ `{hostId,environmentId,path,candidate,base,evidence}` from exact-SHA checks there.
131
+ Never infer local file existence from a remote absolute path.
132
+ Requested settings remain separate from effective native status.providerInstanceId,
133
+ model/options and thread.providerInstanceId/model/runtimeMode. Missing effective
134
+ options or mode is unknown and holds required verification.
135
+
136
+ `inspectFleetReceipt(receipt,{attempt,recommendation,recommendationJSON,now})`
137
+ returns terms, never authority. Existing dispatch-plan --verify can additionally
138
+ consume fleetAttempt/fleetReceipt in its saved envelope; save --out to retain
139
+ full receiptTerms. Legacy configuration-only MATCH checks supplied configuration,
140
+ not native completion. Stable receipt-* / destination-* terms identify the exact
141
+ hold and evidence; unknown means incomplete. A passing receipt is no merge grant.
142
+
143
+ ## Retrieval and retirement
144
+
145
+ The driver retrieves the exact host-qualified report/artifacts and verifies digest
146
+ and byte readback on origin before accepting them. For a reviewer, verify candidate
147
+ HEAD and pinned base commit on the destination, then verify transported commit IDs
148
+ on origin if commits are needed. SSH may transport artifacts/commits or telemetry;
149
+ it never invokes agents or bypasses T3 access. Keep origin and destination paths
150
+ separately qualified in the run record; verify exact SHA/base, not branch names.
151
+
152
+ Remote reviewer retirement is driver-only through axstack-cleanup: re-read the
153
+ exact destination thread/run/ownership, preserve archive plus digest on origin,
154
+ read it back, settle the owned child there and verify settled:true. Retire only
155
+ its recorded T3-created disposable checkout after candidate/base, terminal state
156
+ and ownership checks. Unknown liveness, changed SHA, user takeover or uncertain
157
+ ownership preserves the resource and holds its cleanup. Validate exact literal
158
+ path/realpath/owner/no symlink before deletion; validation must succeed in a
159
+ separate sequential call before deletion. Preserve unknown ignored artifacts.
160
+ Never delete a shared root, use globs/age sweeps or treat an origin path as remote.
161
+ Authors stay until merge/close; driver alone owns forge links/watch/publication.
162
+
163
+ Remote author coordinator is DISABLED after the checkpoint-ref pilot failure;
164
+ no C3b pilot or waiver here. Local isolated authors remain eligible even overloaded.
165
+ See [Fleet verification](fleet-verification.md) for later authorized C3/C6 live
166
+ coverage. C4/C6 personal policy, deployment and live routing are unverified here.
@@ -0,0 +1,325 @@
1
+ # Pure fleet recommendation API (schema 1)
2
+
3
+ C1a exports `recommend(input, now)` and `parseFleetConfig(configText, configDigest)`
4
+ from `../scripts/fleet-route.js`. Both are synchronous Bun APIs. They perform no
5
+ I/O, use no ambient clock and keep no state. Importing the module launches nothing.
6
+ The CLI wraps this API; bounded collection is separate from pure recommendation.
7
+
8
+ ```js
9
+ import { recommend } from '../scripts/fleet-route.js';
10
+ const recommendation = recommend(snapshot, evaluationEpochMs);
11
+ // The driver preserves this JSON, then independently verifies execution authority.
12
+ ```
13
+
14
+ Inputs are JSON values. The same canonical input and integer `now` produce
15
+ byte-identical `JSON.stringify` output. Neither the input nor nested facts are
16
+ mutated or shared with the output. Times are nonnegative epoch milliseconds.
17
+ Errors throw `Error` with a stable leading code, e.g. `config-digest-mismatch`,
18
+ `schema-version`, `assignment-conflict` or `evaluation-time`. CLI mapping of
19
+ these errors belongs to the adapter; exceptions never authorize fallback routes.
20
+
21
+ ## Configuration bytes and policy
22
+
23
+ `configText` contains the exact reviewed UTF-8 JSON bytes, including whitespace.
24
+ `configDigest` is the run-pinned lowercase SHA-256 hex digest of those bytes.
25
+ `parseFleetConfig` verifies the digest before parsing and returns the validated
26
+ object. Missing/invalid configuration and digest mismatches fail explicitly.
27
+ No account path or fleet identity is built into this API.
28
+
29
+ ### Offline command
30
+
31
+ `bun fleet-route.js recommend --input snapshot.json --now 100000 --json`
32
+ reads saved facts plus `--config <path>` (default:
33
+ `~/.config/haoshoku/fleet-routing.json`). It never samples or dispatches.
34
+ `--config-digest <sha256>` pins reviewed config bytes; otherwise the saved
35
+ input must carry `configDigest`. Any saved `configText`/`configDigest` must
36
+ agree with the file and explicit pin. Saved input may omit `configText`.
37
+ `--task <name>` overrides only the request task preference. Evaluation time
38
+ is required and explicit. `--help` lists flags and side effects.
39
+
40
+ With `--json`, stdout is the complete schema-1 result; errors are always
41
+ `{"error":{"code":"stable-code","message":"detail"}}` on stderr with
42
+ empty stdout. Exit 0 means a recommendation/help, 1 means an input/config
43
+ error, and 2 means explicit no-route JSON (when `--json` is used). Without
44
+ `--json`, stdout is a one-line stage/host/reason summary. Authentication stays
45
+ on the actual destination session: required session capabilities are supplied
46
+ evidence, never inferred from the origin catalog or copied credentials.
47
+
48
+ | Config field | Contract |
49
+ | --- | --- |
50
+ | `schemaVersion` | Exactly `1` |
51
+ | `hosts` | Nonempty array of `{id, environmentId, fallbackLimitBp, capabilities}`; both identities unique |
52
+ | `pool` | Unique subset of host IDs; may be empty |
53
+ | `defaultOrder` | Every host ID exactly once |
54
+ | `taskOrders` | Task-name map of complete preference orders, e.g. `playwright`; unknown tasks use default order |
55
+ | `balanceLimitBp` | Integer 0..10000 |
56
+ | `routes` | Ordered array of unique `{id, originEnvironmentId, environmentId, kind, enabled, evidence, authorProof?}` |
57
+
58
+ Each host's `fallbackLimitBp` is an integer 0..10000. Routes target configured
59
+ host environment IDs. `kind` is `local`, `remote` or `remote-author-coordinator`;
60
+ only `local` has equal origin/destination IDs. Multiple routes to one host are
61
+ allowed; the first proven route in configuration order represents that host.
62
+ Only routes whose origin matches the input origin are considered.
63
+
64
+ An `evidence` value is `{environmentId, pointer, observedAt}`. The environment
65
+ must match the fact's destination host; the pointer is an opaque nonempty
66
+ host-qualified reference and observation cannot be in the future. Evidence is
67
+ supplied provenance, never a permission grant. A capabilities map has arbitrary
68
+ names such as `browser:headed`, `worktree-isolation`, `session:sign-in`; each value
69
+ is `{value: true|false|null, evidence}`. Required capabilities must be true and
70
+ proven in both configuration and the destination snapshot.
71
+
72
+ Coordinator routes are exclusively for `author` requests; readers use local/remote
73
+ routes. Remote authors cannot use `remote` routes. A coordinator route is disabled
74
+ unless explicitly enabled with `authorProof`:
75
+ `{originEnvironmentId, environmentId, originVersions, destinationVersions,
76
+ evidence, gatesPassed: true}`. Both version objects contain exact nonempty
77
+ `{t3, axstack}` strings matching current origin/destination snapshots. This
78
+ records the driver's completed pilot gates; the selector cannot prove them.
79
+ Changed versions invalidate the route. Direct remote worktree delegation is
80
+ unsupported; a local author requires `worktree-isolation` automatically.
81
+
82
+ ## Device and browser examples
83
+
84
+ In account-local `taskOrders`, `device` and `playwright` are examples only;
85
+ the user supplies complete host orders in their reviewed configuration.
86
+ Capability names `device:android-emulator`, `browser:headless` and `desktop:user`
87
+ are examples only, using the existing arbitrary-name capability map.
88
+ A device request can require `device:android-emulator`; a headless web request
89
+ can require `browser:headless`, proven at the destination like any capability.
90
+ `desktop:user` describes desktop safety constraints, rather than a requirement
91
+ that would exclude workers; the driver checks it before executing UI work.
92
+ Host identities, preference orders, thermal thresholds and emulator availability
93
+ remain account-local facts, with current evidence collected before execution.
94
+ These examples add no runtime state, built-in task policy or capability inference.
95
+ Follow [UI verification](ui-verification.md#android-device-and-webpwa-dual-run)
96
+ for serial emulator use, desktop safety and declared thermal prechecks.
97
+
98
+ ## Recommendation snapshot
99
+
100
+ | Input field | Contract |
101
+ | --- | --- |
102
+ | `schemaVersion` | Exactly `1` |
103
+ | `configText`, `configDigest` | Exact bytes and run-pinned digest above |
104
+ | `origin` | `{environmentId, versions: {t3, axstack}}` |
105
+ | `request` | `{task, role, requiredCapabilities, repositoryId, providerInstanceId, model, options, runtimeMode}` |
106
+ | `destinations` | One destination snapshot per environment, described below |
107
+ | `routeFacts` | One current observation per route, described below |
108
+ | `telemetry` | At most one sample per environment; absent samples are unknown |
109
+ | `inventoryStatus` | `known` or `unknown` |
110
+ | `inventoryObservedAt` | Required nonfuture timestamp when known; null/absent allowed when unknown |
111
+ | `assignments` | Array of normalized `{assignmentId, environmentId, state}` |
112
+ | `pendingAssignments` | Array of `{clientRequestId, environmentId, taskId?}` |
113
+ | `exclusions` | `{hosts: {hostId: reason}, routes: {routeId: reason}}`; nonempty reason strings |
114
+
115
+ `role` is `reader` (all nonwriter roles) or `author`. `requiredCapabilities`
116
+ is an array of strings. An authentication requirement names the actual session
117
+ capability; only the host proving that session is eligible. No credential
118
+ transfer occurs. `options` is an object keyed by option ID. The adapter must
119
+ normalize T3 option arrays to this map without dropping requested settings.
120
+
121
+ A destination is `{snapshotId, environmentId, status, observedAt, evidence,
122
+ versions, project, execution, capabilities}`. Status must be `known` to be
123
+ eligible; unreadable/unknown facts exclude it. `project` is `{id, repositoryId}`.
124
+ `execution` is `{available: true, providerInstanceId, model, options, runtimeMode}`:
125
+ these are destination-verified facts, never the origin's model catalog. They
126
+ must match the request exactly (option object key order is immaterial).
127
+ Missing projects and mismatched repository/target settings have distinct reasons.
128
+ For dispatch-plan binding, also supply `capabilitiesDigest`: the lowercase
129
+ SHA-256 of the exact raw saved destination `orchestrator_capabilities` JSON
130
+ bytes. The selector carries this optional fact as `destinationCapabilitiesDigest`;
131
+ the adapter requires it. Collect the catalog on that destination/session, not
132
+ from the origin's catalog. Both current `options.values[]` (strings/booleans)
133
+ and legacy `options.options[].id` advertisements are supported.
134
+
135
+ A route observation is `{routeId, reachable, authorized, expiresAt,
136
+ destinationSnapshotId, evidence}`. Reachability and authorization must be true.
137
+ Remote link expiry must be a future epoch timestamp. The destination snapshot ID
138
+ must match the corresponding destination; mismatches exclude the route.
139
+ The driver refreshes access, project and capability observations before dispatch;
140
+ the selector imposes no invented lifetime on those facts. Load alone has the
141
+ 15-second freshness rule. Duplicate snapshot join keys fail schema validation.
142
+
143
+ ## Assignment normalization
144
+
145
+ The driver queries T3 and supplies origin-qualified IDs (e.g. `origin:task-id`)
146
+ and the environment actually doing the work. Remote receipts may have null
147
+ `childThreadId`; use `remoteChild.environmentId`, not the origin, to normalize
148
+ that assignment. The selector does not parse raw T3 receipts or query inventory.
149
+ A coordinator and its nested author must share one normalized assignment ID.
150
+
151
+ States accepted: `pending`, `queued`, `running`, `waiting`, `blocked`,
152
+ `completed`, `failed`, `cancelled`, `canceled`, `rejected`. The last five are
153
+ terminal and do not count. The adapter normalizes T3-native states to these.
154
+ Duplicate identical inventory IDs count once; conflicting identities/states
155
+ throw. Pending records deduplicate by client request ID and by returned
156
+ origin-qualified `taskId` matching `assignmentId`. Even terminal inventory
157
+ records suppress their reflected pending records. Pending IDs on different
158
+ hosts conflict. Update the existing pending record when T3 returns a task ID;
159
+ remove failed/not-submitted selections, retain ambiguous submissions until
160
+ reconciled. An unknown inventory contributes no stale counts; known pending
161
+ records still count. Counts report inventory status/time and pending provenance.
162
+ These are observations, never reservations or predicted resource costs.
163
+
164
+ ### Native inventory and live evidence (C3a)
165
+
166
+ Optional `nativeInventory` is normalized before counts under
167
+ [Fleet dispatch and receipts](fleet-dispatch.md#saved-native-inventory).
168
+ Unknown native totals are null; balance ranking uses the separately labelled
169
+ pendingLowerBound. Counts also retain inventoryReasons/evidence; flags.countsUnknown follows normalized status.
170
+ Optional `nativeDestination` carries observed native forwarded preflight reads.
171
+ The same reference defines terminal receipt validation and its supplied-evidence
172
+ limitation. Live drivers require these observed facts, not synthetic caller claims.
173
+ Legacy normalized input remains unchanged. Remote coordinator remains disabled.
174
+
175
+ ## Bounded collection (C2)
176
+
177
+ Collect with the installed helper (no bin/axstack.js subcommand):
178
+
179
+ ```bash
180
+ bun fleet-route.js sample --fleet manifest.json --config reviewed.json --config-digest <sha256> --json
181
+ bun fleet-route.js sample-recommend --fleet manifest.json --config reviewed.json \
182
+ --config-digest <sha256> --input facts.json --json
183
+ ```
184
+
185
+ `sample` emits `{schemaVersion:1,configDigest,collectedAt,telemetry}`; each sample
186
+ includes its ageMs at collectedAt.
187
+ `sample-recommend` emits `{schemaVersion:1,input,evaluatedAt,recommendation}`;
188
+ its complete `input` and explicit `evaluatedAt` replay through pure `recommend`.
189
+ It replaces saved telemetry, validates all supplied C1 facts before networking,
190
+ and uses the collector clock after collection. It neither refreshes route/project
191
+ facts nor enables a route. Both commands require a run-pinned digest; config
192
+ path defaults as above. They write stdout/stderr only. Sample reports are JSON
193
+ with or without `--json`; this flag is accepted for interface consistency.
194
+
195
+ The caller supplies a separate manifest, never private fleet defaults:
196
+
197
+ ```json
198
+ {"schemaVersion":1,"hosts":[
199
+ {"hostId":"worker-a","environmentId":"env-worker-a","transport":"local"},
200
+ {"hostId":"worker-b","environmentId":"env-worker-b","transport":"ssh","destination":"sample-worker","user":"fixture_user"}
201
+ ]}
202
+ ```
203
+
204
+ Only these keys are accepted. `hosts` has 1..64 entries, unique host IDs and
205
+ SSH destinations; each host/environment pair must match reviewed config.
206
+ At most one entry is local. Unlisted hosts have unknown telemetry. The caller
207
+ must bind the local identity to its collecting host; a manifest grants no T3
208
+ identity or execution authority. Local entries cannot carry SSH fields.
209
+ SSH destinations are lowercase MagicDNS short names (1..63 ASCII letters,
210
+ digits or interior hyphens, starting/ending alphanumeric, not all digits).
211
+ No aliases with punctuation, addresses, FQDNs, user@host, ports, options or
212
+ commands are accepted. Optional users match `[a-z_][a-z0-9_-]{0,31}`.
213
+ Existing SSH configuration/identities and known-host records must already exist;
214
+ no key paths, credentials, enrollment or interactive prompts are accepted.
215
+
216
+ `--interval-ms` defaults to 5000, integer range 5000..60000. All manifest hosts
217
+ start concurrently in one batch; each waits the interval after its first read
218
+ returns, then reads again. `--timeout-ms` defaults to 10000, integer range
219
+ interval+1000..120000, covering both reads, transport and waiting per host.
220
+ Long intervals require an explicit larger timeout. Timed-out/cancelled SSH
221
+ processes are killed and reaped, waits cancelled and listeners/timers removed.
222
+ SIGINT/SIGTERM cancel the owned collection and produce unknown telemetry.
223
+ SSH uses argv, noninteractive strict host-key checks and existing identities;
224
+ the only remote command is a constant read of /proc/stat and /proc/meminfo.
225
+ Output is capped at 64KiB per read; stderr details and credentials are not emitted.
226
+ No agent, daemon, schedule, dispatch or persistent collection state is created.
227
+
228
+ Linux CPU uses the delta of the first eight aggregate /proc/stat counters.
229
+ Busy includes steal and excludes idle/iowait; guest/guest_nice are already in
230
+ user/nice and are not added again. RAM uses MemTotal minus MemAvailable:
231
+ `ramBp` is the upward-rounded mean of the first and latest endpoint pressure,
232
+ `ramLatestBp` is latest pressure. This is an endpoint mean, not continuous RAM
233
+ history. Missing/duplicate/malformed fields, counter regression, zero elapsed
234
+ CPU ticks or changed MemTotal produce unknown, without partial pressure values.
235
+
236
+ Each collected sample contains host/environment IDs, status, source
237
+ `linux-proc-local` or `linux-proc-ssh`, `scope:whole-host`, intervalMs, timeoutMs,
238
+ durationMs, windowStartedAt and receivedAt. Success additionally contains
239
+ windowEndedAt, cpuBp, ramBp and ramLatestBp. `windowKind:collector-bracket`
240
+ means the collector-clock window brackets both reads, transport and the wait;
241
+ remote counter read times cannot be located more precisely within that bracket.
242
+ `durationMs` uses monotonic elapsed time (integer milliseconds), not a claimed
243
+ native history length. Both sources read whole-host Linux counters, never T3
244
+ process metrics or the cached 200ms delta. No fresh 30-second history is claimed.
245
+ Long windows retain their actual duration. All wall timestamps come from the
246
+ collecting host; remote clocks are neither read nor used for age.
247
+
248
+ A backwards collector clock, shortened window or forward jump over one second
249
+ relative to monotonic elapsed time produces collector-clock unknown. Per-host
250
+ statuses timeout, cancelled, malformed, inconsistent-counters, collector-clock
251
+ and failed become C1 unknown reasons. Collection failure does not fabricate an
252
+ access failure or load-based hold. C1 preserves source/window/duration and age
253
+ for unknown telemetry where supplied. Offline historical C1 inputs may omit
254
+ window timestamps and interval; when present, start/end must enclose duration,
255
+ end cannot follow receipt, and interval must be 5000..60000 and <=duration.
256
+ Age is solely evaluation time minus collector receipt, including stale/future
257
+ unknown results. Re-run before dispatch; snapshots reserve no capacity.
258
+
259
+ CPU/RAM basis points must be integers 0..10000. Instead of either `cpuBp` or
260
+ `ramBp`, supply `cpuFraction` or `ramFraction` (finite 0..1). Exactly one
261
+ representation per metric is allowed. Fractions round upward using their
262
+ shortest decimal representation, avoiding binary multiplication overshoot.
263
+ `ramLatestBp`, when supplied, also participates in the maximum pressure so an
264
+ average cannot hide recent RAM pressure. Load is the max of the supplied CPU
265
+ and RAM pressure; no role costs, GPU or personal-use gates exist.
266
+
267
+ Missing, malformed, short-window, non-whole-host, failed and timed-out samples
268
+ become unknown, as do ages >15000ms or <0. Age 15000ms remains measured. Unknown
269
+ load does not exclude an otherwise eligible route. Additional collector fields
270
+ (e.g. remote wall clock or detailed counter provenance) do not override the
271
+ collector receipt timestamp or validated pressure fields.
272
+
273
+ ## Result and downstream binding
274
+
275
+ The result has `schemaVersion`, `configDigest`, `evaluatedAt`,
276
+ `originEnvironmentId`, `request`, `stage`, `reason`, `decisionReason`, `selected`,
277
+ `flags`, `counts`, `eligible`, `excluded`. Stage is exactly `balance`,
278
+ `pool-unknown`, `fallback` or `no-route`. No-route has `selected: null`.
279
+ `reason: author-local-only` explicitly exposes a local-author limitation;
280
+ `decisionReason` retains the underlying load choice.
281
+
282
+ `selected` and each eligible host contain `hostId`, `environmentId`, `projectId`,
283
+ `repositoryId`, `routeId`, `routeKind`, `destinationSnapshotId`,
284
+ `destinationObservedAt`, `destinationEvidence`, `routeEvidence`, `versions`,
285
+ `execution`, `capabilities`, `fallbackLimitBp`, `assignmentCount`, `telemetry`.
286
+ Measured telemetry includes age, CPU/RAM, maximum pressure, source and window;
287
+ unknown telemetry carries its reason and age when available.
288
+
289
+ `counts.byEnvironment` maps configured environments to observed distinct active
290
+ assignment counts. Flags expose `countsUnknown`, `poolEmpty`,
291
+ `poolPartlyUnknown`, `telemetryUnknown`, `overloaded`, `localAuthorOnly`.
292
+ Excluded entries include host/route identity, reason codes, explicit exclusion
293
+ text and available configured/observed evidence. Failed alternative routes may
294
+ appear even when another route on that host is eligible.
295
+
296
+ ### Dispatch-plan adapter
297
+
298
+ Add `--recommendation recommendation.json --fleet-input snapshot.json
299
+ --now 100000 --config-digest <run-pin>` to the existing dispatch-plan flags.
300
+ All four are required together. `--capabilities` names the raw destination
301
+ catalog. The complete selector input includes config bytes; retain the CLI's
302
+ saved input plus the reviewed `configText` if the CLI supplied it from a file.
303
+ Recompute using the same explicit time and task before binding. The adapter
304
+ rejects any recommendation disagreement, missing/mismatched catalog digest,
305
+ unavailable destination model/options, or role/settings/config disagreement.
306
+ It never reinterprets a requested model pin as a different available model.
307
+
308
+ Task plans add `environmentId`/`projectId` to `target`; launch plans add them
309
+ beside `modelSelection`. All requested options survive as T3 option entries.
310
+ Both include `fleetBinding: {configDigest, destinationSnapshotId,
311
+ destinationCapabilitiesDigest}`. This is preserved evidence, not a T3 launch
312
+ parameter. Copy only tool-supported launch fields. Coordinator author routes
313
+ remain explicitly unsupported pending C3 lifecycle enablement.
314
+
315
+ For `--verify`, supply the effective configuration plus independently observed
316
+ `environmentId`, `projectId` and the preserved `fleetBinding` in one JSON
317
+ envelope. All are compared; missing values hold. Do not synthesize observed
318
+ fields from the plan. C3 owns live evidence collection and remote receipts;
319
+ C1b verifies supplied evidence only and cannot authenticate its origin.
320
+ Fleet binding mismatches exit 2; malformed CLI/files exit 1. Existing local
321
+ planning without these flags retains its interface.
322
+
323
+ A recommendation never launches,
324
+ provisions, mutates links, retries submission or grants authority. Call afresh
325
+ before each dispatch; assignment/load snapshots remain subject to races.