ace-hitl 0.17.0 → 0.23.2

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +162 -0
  3. data/README.md +60 -117
  4. data/docs/usage.md +247 -421
  5. data/handbook/workflow-instructions/hitl.wf.md +55 -6
  6. data/lib/ace/hitl/adapters/registry.rb +65 -0
  7. data/lib/ace/hitl/adapters/terminal.rb +75 -0
  8. data/lib/ace/hitl/adapters.rb +80 -0
  9. data/lib/ace/hitl/atoms/hitl_address.rb +89 -0
  10. data/lib/ace/hitl/atoms/hitl_event_text.rb +54 -0
  11. data/lib/ace/hitl/cli/commands/create.rb +81 -12
  12. data/lib/ace/hitl/cli/commands/lifecycle_command.rb +1 -7
  13. data/lib/ace/hitl/cli/commands/list.rb +22 -1
  14. data/lib/ace/hitl/cli/commands/send.rb +85 -0
  15. data/lib/ace/hitl/cli/commands/serve.rb +25 -44
  16. data/lib/ace/hitl/cli/commands/show.rb +23 -3
  17. data/lib/ace/hitl/cli/commands/update.rb +4 -0
  18. data/lib/ace/hitl/cli/commands/wait.rb +5 -0
  19. data/lib/ace/hitl/cli.rb +8 -18
  20. data/lib/ace/hitl/lifecycle/frame_serving.rb +0 -15
  21. data/lib/ace/hitl/lifecycle/kinds.rb +1 -1
  22. data/lib/ace/hitl/lifecycle/lifecycle_requests.rb +0 -36
  23. data/lib/ace/hitl/lifecycle/policy.rb +0 -44
  24. data/lib/ace/hitl/lifecycle/protocol.rb +1 -2
  25. data/lib/ace/hitl/lifecycle/service.rb +2 -3
  26. data/lib/ace/hitl/lifecycle/store.rb +11 -75
  27. data/lib/ace/hitl/lifecycle/tls_access_policy.rb +0 -14
  28. data/lib/ace/hitl/lifecycle/tls_project_scope.rb +0 -15
  29. data/lib/ace/hitl/lifecycle/tls_service.rb +2 -3
  30. data/lib/ace/hitl/lifecycle.rb +2 -4
  31. data/lib/ace/hitl/live_client.rb +11 -87
  32. data/lib/ace/hitl/models/hitl_event.rb +35 -0
  33. data/lib/ace/hitl/molecules/hitl_address_resolver.rb +80 -0
  34. data/lib/ace/hitl/molecules/hitl_config_loader.rb +22 -1
  35. data/lib/ace/hitl/molecules/hitl_creator.rb +126 -27
  36. data/lib/ace/hitl/molecules/hitl_delivery_state.rb +58 -0
  37. data/lib/ace/hitl/molecules/hitl_display_formatter.rb +3 -2
  38. data/lib/ace/hitl/molecules/hitl_loader.rb +19 -2
  39. data/lib/ace/hitl/molecules/hitl_route_loader.rb +108 -0
  40. data/lib/ace/hitl/organisms/hitl_manager.rb +73 -6
  41. data/lib/ace/hitl/organisms/hitl_sender.rb +148 -0
  42. data/lib/ace/hitl/providers/lab.rb +15 -129
  43. data/lib/ace/hitl/version.rb +1 -1
  44. data/lib/ace/hitl.rb +8 -1
  45. metadata +12 -43
  46. data/lib/ace/hitl/cli/commands/ask.rb +0 -136
  47. data/lib/ace/hitl/cli/commands/overseer_ack.rb +0 -30
  48. data/lib/ace/hitl/cli/commands/overseer_pending.rb +0 -28
  49. data/lib/ace/hitl/cli/commands/overseer_send.rb +0 -35
  50. data/lib/ace/hitl/cli/commands/proposal.rb +0 -75
  51. data/lib/ace/hitl/lifecycle/overseer.rb +0 -111
  52. data/lib/ace/hitl/lifecycle/proposals.rb +0 -297
  53. data/lib/ace/hitl/lifecycle/service_publication_binding.rb +0 -97
  54. data/lib/ace/hitl/proposals/evaluator.rb +0 -29
  55. data/lib/ace/hitl/proposals/policy.rb +0 -127
  56. data/lib/ace/hitl/providers/lab/assignment_binding.rb +0 -126
  57. data/lib/ace/hitl/providers/lab/protected_assignment_binding.rb +0 -75
  58. data/lib/ace/hitl/providers/providers.rb +0 -33
data/docs/usage.md CHANGED
@@ -1,35 +1,36 @@
1
1
  ---
2
2
  doc-type: user
3
3
  title: ace-hitl Usage Guide
4
- purpose: Practical CLI usage reference for ace-hitl event creation, triage, and resolution
5
- flows.
4
+ purpose: Practical CLI reference for local HITL event creation, triage, and answer flows.
6
5
  ace-docs:
7
- last-updated: '2026-04-02'
8
- last-checked: '2026-04-02'
6
+ last-updated: 2026-10-17
7
+ last-checked: 2026-10-17
9
8
  ---
10
9
 
11
10
  # ace-hitl Usage
12
11
 
12
+ > **State owner:** `.ace-local/hitl/` -- HITL event markdown files owned by
13
+ > `ace-hitl`. See [docs/lab-flow.md](../../docs/lab-flow.md).
14
+
13
15
  Canonical handbook resources:
14
16
 
15
17
  - Workflow: `wfi://hitl`
16
18
  - Skill: `as-hitl`
17
19
 
18
- Runtime store default: `.ace-local/hitl/` (legacy `.ace-hitl/` is no longer used as default).
19
-
20
- ## Testing
20
+ Runtime store default: `.ace-local/hitl/`.
21
21
 
22
- `ace-hitl` includes isolated fast tests and deterministic scoped integration tests.
22
+ ## Event files
23
23
 
24
- - Deterministic coverage lives under `test/fast/`.
25
- - This package does not introduce `test/feat/` or `test/e2e/` in this migration.
24
+ Each event is a markdown file with frontmatter (`id`, `title`, `kind`,
25
+ `status`, `tags`, `assignment`, `step`, `resume`) and the question/answer
26
+ sections. Folders:
26
27
 
27
- Verification commands:
28
+ - `.ace-local/hitl/` -- active events (`next`)
29
+ - `.ace-local/hitl/archive/` -- archived events
28
30
 
29
- ```bash
30
- ace-test ace-hitl
31
- ace-test ace-hitl all
32
- ```
31
+ `--scope current` reads only the current worktree's events; `--scope all` reads
32
+ the main checkout's operator view. Without `--scope`, lookup is local-first and
33
+ falls back across worktrees when smart scope is active.
33
34
 
34
35
  ## Create
35
36
 
@@ -38,254 +39,261 @@ ace-hitl create "Which auth strategy?" \
38
39
  --kind decision \
39
40
  --question "JWT or sessions?" \
40
41
  --question "Refresh token storage?" \
41
- --assignment 8qr5kx \
42
+ --assignment abc123 \
42
43
  --step 020 \
43
44
  --step-name implement-auth \
44
- --resume "/as-assign-drive 8qr5kx"
45
+ --resume "/as-assign-drive abc123"
45
46
  ```
46
47
 
47
- Common completion-attention handoff:
48
+ Kinds: `clarification`, `decision`, `approval`. Completion handoff uses
49
+ `--step completion`:
48
50
 
49
51
  ```bash
50
52
  ace-hitl create "Review completed assignment results" \
51
53
  --kind approval \
52
- --question "Please confirm next action for 8qr5kx." \
53
- --assignment 8qr5kx \
54
- --step completion \
55
- --step-name assignment-complete \
56
- --resume "/as-assign-drive 8qr5kx"
54
+ --question "Please confirm next action for abc123." \
55
+ --assignment abc123 --step completion
57
56
  ```
58
57
 
59
- ## List
60
-
61
- `ace-hitl list` is local-first by default:
58
+ ## Recipient addressing and Reply
62
59
 
63
- - in a linked worktree: behaves like `--scope current`
64
- - in the main checkout: behaves like `--scope all`
65
- - if `--status` is omitted: includes all statuses in the selected folder scope
60
+ Every event can carry an explicit recipient address read from the `hitl`
61
+ configuration cascade (`.ace/hitl/config.yml` project override; HITL reads
62
+ the address config — consumers pass a configured name, no client-side
63
+ routing):
66
64
 
67
- ```bash
68
- ace-hitl list
69
- ace-hitl list --scope current
70
- ace-hitl list --scope all
71
- ace-hitl list --status pending
72
- ace-hitl list --kind decision
73
- ace-hitl list --kind clarification --status pending
74
- ace-hitl list --tags auth,security
75
- ace-hitl list --in archive
65
+ ```yaml
66
+ hitl:
67
+ machine: lab01 # this machine's identity for addresses
68
+ default_recipient: overseer # optional name applied when --to is absent
69
+ recipients:
70
+ overseer: # panel form: machine + panel (route-deliverable)
71
+ machine: lab01
72
+ panel: overseer # the pane/channel the recipient reads
73
+ captain: # panel form on another machine
74
+ machine: lab02
75
+ panel: telegram
76
+ archive: # folder form: machine + events folder (+ case ID)
77
+ machine: lab01
78
+ folder: .ace-local/hitl
76
79
  ```
77
80
 
78
- ## Show
79
-
80
- `ace-hitl show` also accepts `--scope current|all`.
81
- Without `--scope`, lookup is local-first; if not found and smart scope is active, it retries across all worktrees.
82
- When lookup resolves outside the current worktree, output includes an explicit `Resolved Location:` line.
83
- When using `--scope all`, ambiguous matches return an error with candidate paths so the operator can select the intended event explicitly.
84
-
85
81
  ```bash
86
- ace-hitl show abc123
87
- ace-hitl show abc123 --scope current
88
- ace-hitl show abc123 --scope all
89
- ace-hitl show abc123 --path
90
- ace-hitl show abc123 --content
82
+ ace-hitl create "Blocked on auth" --kind decision --to overseer --task 8xg.t.sbz.1.1
91
83
  ```
92
84
 
93
- ## Update
85
+ - The folder form stamps the new event's ID as the case ID; the panel form
86
+ carries no case ID. Questions additionally record a `reply_to` address
87
+ (sender machine + events folder + own case ID), so an answer binds to the
88
+ original case and the right agent — task/assignment/step are context,
89
+ never an ID substitute.
90
+ - Route delivery (`send`) requires a panel-form recipient. A folder-form
91
+ recipient has no shipped delivery adapter: selected adapters refuse it per-route
92
+ with a clear `failed` reason (the event file already sitting in a folder
93
+ is a local write, not a delivery confirmation). Refusal semantics are
94
+ strict on both axes of the address: the terminal adapter delivers only
95
+ when the recipient's `machine` matches the route's machine (default
96
+ `hitl.machine`) and its `panel` names the target pane; the hermes
97
+ adapter delivers only when its channel folder is the recipient's target
98
+ (panel equal to the channel name on the route's machine). A route never
99
+ silently sends to its own configured destination for a recipient it
100
+ cannot serve.
101
+ - The `reply_to` folder is the resolved absolute sender events folder (not
102
+ the configured `root_dir` setting, which stays relative), so two
103
+ worktrees on one machine never share a reply address.
104
+ - Local Reply is unchanged: `update <ref> --answer` binds by exact ID with
105
+ strict ambiguity handling and an explicit not-found error; a transport
106
+ later reads `reply_to` from the answered event instead of searching for the
107
+ "last question".
108
+ - Unknown recipient names, malformed configured addresses, and a missing
109
+ `hitl.machine` (needed for `reply_to`) fail closed before anything is
110
+ written. Ambiguous shortcuts list candidates instead of guessing.
111
+ - `--to` absent with no `default_recipient` creates a local-only event with
112
+ no address keys (legacy flow unchanged). `show` prints `Recipient:` and
113
+ `Reply-To:` when present; local write is not delivery (adapters and
114
+ redelivery land in the remaining L2 leaves).
115
+
116
+ ## Reports
117
+
118
+ The same interface records reports — completion or error notifications that do
119
+ not await an answer:
94
120
 
95
121
  ```bash
96
- ace-hitl update abc123 --set status=in-progress
97
- ace-hitl update abc123 --add tags=reviewed
98
- ace-hitl update abc123 --remove tags=stale
99
- ace-hitl update abc123 --answer "Use JWT with server-side refresh tokens."
100
- ace-hitl update abc123 --move-to archive
101
- ace-hitl update abc123 --move-to next
102
- ace-hitl update abc123 --answer "close the assignment" --resume
122
+ ace-hitl create "Work done" \
123
+ --type report --result success \
124
+ --body "Implemented JWT login" \
125
+ --body "Tests green" \
126
+ --task 8xg.t.sbz.1.0 --assignment abc123 --step 010
127
+
128
+ ace-hitl create "Step failed" \
129
+ --type report --result error \
130
+ --body "Verifier crashed after merge" \
131
+ --task 8xg.t.sbz.1.0 --step 020
103
132
  ```
104
133
 
105
- ## Ask and scoped live client
134
+ Rules:
106
135
 
107
- ```bash
108
- ace-hitl ask --question "Proceed with deploy?" \
109
- --assignment assign685 --attempt attempt685 --project ace \
110
- --effect-arg /usr/bin/notify-send --effect-arg "{answer}" --effect-cwd /tmp
111
- ```
136
+ - `--type report` requires `--result success|error`; `--question`, `--kind`,
137
+ and `--resume` are rejected on reports.
138
+ - `--result` and `--body` are only valid with `--type report`.
139
+ - Reports are created with status `recorded` and never appear as pending
140
+ questions: `wait <report>`, `update <report> --answer`, and
141
+ `update <report> --resume` exit 1 with an explicit error. Generic updates
142
+ (`--set`, `--add`, `--remove`, `--move-to archive`) still work.
143
+ - `--task` is accepted for both questions and reports.
144
+ - Local write is not delivery: `recorded` means the event is persisted under
145
+ `.ace-local/hitl/`, not that it reached the recipient. Remote addressing and
146
+ configurable adapters are planned follow-up leaves (8xg.t.sbz.1.1–.1.3).
112
147
 
113
- Assignment and attempt are required compact managed IDs. The coordinator verifies
114
- an active owner and its exact native reverse binding, using the kernel peer PID
115
- and Runtime's existing process ancestry/birth authority. Missing peer PID or
116
- native evidence refuses an exact-owner claim. Darwin uses LOCAL_PEERPID; Linux
117
- uses SO_PEERCRED. UID equality alone never selects another attempt. There is no
118
- Work binding, Lab daemon socket or environment-derived reverse target.
148
+ Questions keep the existing pending → answered cycle; files without a `type`
149
+ frontmatter key load as questions.
119
150
 
120
- An agent explicitly hosts its watcher in its own process:
151
+ ## Send — delivery routes
121
152
 
122
- ```ruby
123
- client = Ace::Hitl::LiveClient.new(root: checkout_root)
124
- watcher = client.watch(request: request_id) { |delivery| handle_queue_result(delivery) }
125
- watcher.value
126
- client.status(request: request_id)
127
- ```
153
+ `create` persists locally; `send` is the explicit delivery step through the
154
+ configured adapter routes. Switching transports is pure configuration — no
155
+ assignment or coworker logic changes.
128
156
 
129
- `deliver` consumes an authorized ordinary answer, enqueues one incarnation-bound
130
- Herdr event, registers its original assignment/attempt attribution with Assign,
131
- and sends to the exact selected terminal. The accepted owner's terminal, agent
132
- and native session ID are checked against the live Herdr pane before sending.
133
- A replaced agent in the same pane refuses rather than selecting another target.
134
- Repeated calls reuse the same event; uncertain sends are not automatically
135
- repeated. The configured Hermes transport owns Telegram request/reply routing.
136
-
137
- A delivered acknowledgement means submitted to the terminal, not read by the
138
- agent. LiveClient.status reads delivery history from the original Inbox owner;
139
- delivered answers no longer appear as pending human attention. Current agent
140
- progress/output comes from direct Herdr/tmux panel capture when needed.
141
-
142
- Business effects still run through their scoped owner with their own result
143
- and authorization. Neither a sent message nor panel output replaces verification
144
- of those effects. No dedicated Codex app-server, message-read signer/key or
145
- signed consumption/supersession reconciliation API is required.
146
-
147
- The shared versioned envelope is semantically owned by HITL and packaged in
148
- `ace-hitl-contract` to preserve the acyclic HITL → Assign → Herdr → contract
149
- graph. Its nested Hermes message and reverse reference are distinct schemas.
150
- OTP answers and their hashes are excluded from the envelope and native delivery.
151
-
152
- ## Scoped Store Boundary (spec 8wq.t.34i)
153
-
154
- The relay request store is the PRIVATE state of one authenticated
155
- boundary service. `ace-hitl serve` owns the store and speaks a bounded
156
- JSON protocol over a UNIX socket; every client operation
157
- (`ask`/`deliver`/`consume`/`cancel`/`pending`/`states`/`duty`/`read`)
158
- runs through the authenticated boundary client — the CLI never touches
159
- shared store files directly.
160
-
161
- Identity is a kernel fact: the service resolves each connection's peer
162
- uid from the OS (`getpeereid`), and payload fields or environment
163
- variables can never name a requester. Clients authenticate the
164
- endpoint back: the socket must be owned by the trusted service uid,
165
- must not be world-writable, and the connected peer must BE the service
166
- uid. Authorization facts (service uid, transport uids, per-project
167
- Captain visibility) come only from the trusted deployment grants
168
- document — the same file and traversal trust rules as ace-lab
169
- (default `ACE_HITL_GRANTS_PATH`, `/etc/lab/ace-lab/authorization.yml`).
157
+ ```yaml
158
+ hitl:
159
+ adapters:
160
+ routes:
161
+ - route: overseer-terminal # display name; defaults to the adapter name
162
+ recipients: [overseer] # configured names resolved to the stored destination
163
+ adapter: terminal # built-in: any ace-runtime terminal
164
+ runtime: herdr # tmux | herdr
165
+ pane: p5 # opaque pane handle (runtime-owned)
166
+ machine: lab01 # defaults to hitl.machine; recipient machine must match
167
+ - route: captain-telegram
168
+ recipients: [captain]
169
+ adapter: hermes # ships in ace-hitl-hermes
170
+ channel: project
171
+ machine: lab01 # defaults to hitl.machine
172
+ folder: /run/hermes/project
173
+ sender: lab01 # defaults to machine
174
+ ```
170
175
 
171
176
  ```bash
172
- # Deployment (root runs the service under its own account):
173
- ace-hitl serve --socket /run/ace-hitl-project/lifecycle.sock \
174
- --store-root /var/lib/ace-hitl-project --group ace-hitl-project
175
- # ACE_HITL_SOCKET, ACE_HITL_STORE_ROOT and ACE_HITL_GRANTS_PATH may supply paths.
177
+ ace-hitl send 8xhkep
176
178
  ```
177
179
 
178
- Give each authority a dedicated service/root-owned, non-world-writable socket
179
- directory and a dedicated control group. Do not place its socket in `/run/lab`
180
- or another directory containing unrelated service sockets: granting the
181
- transport access to that directory's shared group can expose those endpoints.
182
-
183
- Store layout (all service-owned): `0711` traverse-only root;
184
- `requests/ answers/ secrets/ effects/ locks/ terminals/` are `0700`;
185
- `public/` is `0755` with `0440` non-secret projections. There is no
186
- chmod-to-world-writable mode: requesters receive answer bytes over the
187
- authenticated connection, never through file ownership.
188
-
189
- Roles:
190
-
191
- - requester: create, consume and cancel its OWN requests (request
192
- facts come back from `ask`/`consume`; the boundary `read` protocol
193
- operation is available to library clients);
194
- - configured transport (grants `hitl.transport_uids` + principals):
195
- `deliver`, `pending`, `states`, `duty`;
196
- - unknown identity is an error, never permission.
197
-
198
- Failures are classified end to end: `PermissionError`,
199
- `BindingError`, `StateError`, `AnswerError`, and `TransportError`
200
- (unavailable/untrusted boundary, deadline, malformed frame) — transport
201
- failures are visible and recoverable, and duplicate consume/cancel are
202
- idempotent (the committed receipt replays; a conflicting transition is
203
- an error).
204
-
205
- ## Relay Lifecycle (Generic HITL Request Store)
206
-
207
- The generic relay request lifecycle is native to the gem
208
- (`Ace::Hitl::Lifecycle`; migration spec 8wm.t.y21, scoped by
209
- 8wq.t.34i). Requests never expire on a timer, every terminal
210
- transition shares one stable per-request lock, and the store root is
211
- `ACE_HITL_STORE_ROOT` (default `/run/lab/hitl`); the Overseer channel
212
- root is `ACE_HITL_OVERSEER_CHANNEL_ROOT` (default
213
- `/lab/state/overseer-channel`). Machine output is one JSON line.
214
-
215
- Managed binding (the default authority):
180
+ ```
181
+ HITL event 8xhkep sent through 1 route(s):
182
+ overseer-terminal (terminal): delivered - text pushed to herdr pane p5
183
+ ```
184
+
185
+ Rules:
186
+
187
+ - For an addressed event, only routes whose `recipients` resolve to its exact
188
+ stored machine and panel/folder are selected. Changing a configured alias does
189
+ not redirect an existing case. Missing selectors do not match addressed events;
190
+ no match fails before adapter invocation or delivery-state writes. Recipient-less
191
+ events use the explicitly configured route set.
192
+ - Several matching routes deliver to the same intended recipient independently.
193
+ Unmatched routes are not invoked or recorded as failures. One result line per
194
+ selected route; a route's success never hides another selected route's
195
+ failure. Exit 0 only when every route is `delivered`; exit 1 on any
196
+ `failed`/`unavailable` route, no configured routes (concrete error naming
197
+ `hitl.adapters.routes`), or unknown/ambiguous ref. An unknown adapter on
198
+ one route no longer aborts the send: that route is retained as
199
+ `unavailable` with the resolution error as its reason, the remaining
200
+ routes still deliver, and the send still exits 1.
201
+ - Route names must be a single token matching `[a-z0-9][a-z0-9_-]*`
202
+ (no dots): delivery retention keys route names literally, and `send`
203
+ rejects a dotted name (e.g. `captain.telegram`) before any delivery
204
+ attempt.
205
+ - Every route receives the identical shared rendering: `[HITL question]`/
206
+ `[HITL report <result>]` prefix, one `Q:` line per question, `Reply-To:`
207
+ on questions only, task/assignment/step context.
208
+ - `delivered` means accepted by the route's transport. An unavailable
209
+ adapter never reroutes to another recipient.
210
+ - Native terminal handles such as `w7:p1` are accepted unchanged as `panel`.
211
+ This does not relax the machine-token or folder-traversal rules.
212
+ - Adding an adapter requires no ace-hitl change: a gem ships
213
+ `lib/ace/hitl/adapters/<name>.rb` calling `Ace::Hitl::Adapters.register`
214
+ (duck contract: `factory.call(route)` → adapter with
215
+ `deliver(event:, text:, recipient:, reply_to:)` returning a
216
+ `RouteResult`; status `:delivered`/`:failed`/`:unavailable`). Nervus is
217
+ the out-of-scope future example.
218
+
219
+ ### Retention and redelivery
220
+
221
+ Per-route outcomes are persisted on the event's own file
222
+ (`delivery.<route>` frontmatter) — undelivered messages and their failure
223
+ reasons stay in the local HITL files; there is no journal or queue.
216
224
 
217
225
  ```bash
218
- ace-hitl ask --question "Choose the next scope" \
219
- --assignment 8x3abc --attempt a1b2c3 --project ace
226
+ ace-hitl send 8xhkep # retries undelivered/unknown routes only
227
+ ace-hitl send 8xhkep --force # redeliver every route (same case ID)
228
+ ace-hitl send 8xhkep --dry-run # show retry/skip decisions, no delivery
229
+ ace-hitl list --delivery undelivered
230
+ ace-hitl show 8xhkep # Delivery: <route>: <status> - <reason>
220
231
  ```
221
232
 
222
- The request binds to the exact ACTIVE MANAGED attempt of the calling
223
- identity, verified through the ace-assign coordinator under the
224
- assignment exclusion: a stale, ended, replaced, or uncertain attempt
225
- cannot acquire authority, and the exclusion is HELD across every
226
- consume/deliver transition so an attempt cannot end between the
227
- liveness check and the commit. Assignment and attempt identity are always explicit;
228
- there is no legacy Work-only contract.
233
+ - Confirmed `delivered` routes are skipped by default; a redelivery of a
234
+ fully delivered event is a no-op success (`use --force to redeliver`).
235
+ - A crashed `send` leaves an `in_flight` marker = unknown result; the next
236
+ `send` retries it. This is at-least-once: a retry after an unknown result
237
+ may duplicate, and every rendering carries the same `(case <ID>)` and task
238
+ binding so the recipient dedups by ID. Exactly-once is not promised.
239
+ - Redelivery retries the *delivery of the same case* — same event ID, same
240
+ content, same task/assignment binding — never the assignment or its next
241
+ step.
242
+ - The delivery filters are record-relative: an event that was never sent
243
+ appears in neither `--delivery` filter, and a route added to the
244
+ configuration after a full delivery does not flip a `delivered` event
245
+ back to `undelivered` (the next `send` still attempts the new route).
229
246
 
230
- Transport side:
247
+ ## List
231
248
 
232
249
  ```bash
233
- ace-hitl pending # answerable requests
234
- ace-hitl states # all public lifecycle projections
235
- ace-hitl duty # pending + escalated projection
236
- ace-hitl deliver hitl-0a1b2c3d4e5f6708 <<< "approved"
250
+ ace-hitl list
251
+ ace-hitl list --scope current
252
+ ace-hitl list --scope all
253
+ ace-hitl list --status pending
254
+ ace-hitl list --kind decision
255
+ ace-hitl list --type question
256
+ ace-hitl list --type report
257
+ ace-hitl list --delivery undelivered
258
+ ace-hitl list --delivery delivered
259
+ ace-hitl list --tags auth,security
260
+ ace-hitl list --in archive
237
261
  ```
238
262
 
239
- `deliver` reads the answer from stdin and relays it unchanged. OTP
240
- bytes go to the service's memory vault; plain answers to the
241
- service-owned `0600` answers file. Liveness is re-verified under the
242
- lock, and the declared effect callback (non-OTP kinds only) executes:
243
- exec-style argv (never a shell), `{answer}` substituted once per
244
- element, optional fullmatch regex gate, bounded timeout, attempts
245
- logged redacted in the effects log, and one deduped escalation with
246
- `effect_state: callback-escalated` in the public projection on failure
247
- (`callback-ok` on success). Duplicate deliveries are idempotent: the
248
- committed answer is reported without re-running any effect.
263
+ With no `--status`, all statuses in the selected folder scope are listed.
249
264
 
250
- Requester side:
265
+ ## Show
251
266
 
252
267
  ```bash
253
- ace-hitl consume hitl-0a1b2c3d4e5f6708
254
- ace-hitl consume hitl-0a1b2c3d4e5f6708 --timeout 600
255
- ace-hitl cancel hitl-0a1b2c3d4e5f6708 --reason "operator stopped the work"
268
+ ace-hitl show abc123
269
+ ace-hitl show abc123 --scope all
270
+ ace-hitl show abc123 --path
271
+ ace-hitl show abc123 --content
256
272
  ```
257
273
 
258
- A consume timeout bounds ONLY the local wait — the request stays
259
- pending and answerable. OTP consumption requires the challenge's
260
- authorized operation (`--operation <name>`); the secret transfers
261
- exactly once and a consumed retry replays the receipt WITHOUT the
262
- bytes. The persisted challenge deadline is checked at the locked handoff;
263
- consumption at or after that deadline refuses with an expired error and
264
- discards the pending secret. Memory retention ends at the earlier of the
265
- challenge deadline and the vault TTL. Delivery, duplicate delivery, waits,
266
- and service restarts cannot extend the authorized window. Request a fresh
267
- authorized challenge after expiry. Cancel is the ONLY way to abandon a request, requester-only;
268
- it records `cancelled_by` and the `reason` in the public projection,
269
- and late answers/consumes fail closed against the committed receipt.
270
-
271
- Overseer reverse address (bounded, type-tagged responses):
274
+ When lookup resolves outside the current worktree, output includes an explicit
275
+ `Resolved Location:` line. Ambiguous matches under `--scope all` list candidate
276
+ paths instead of guessing. Report events print `Type: report`, `Result:`, and
277
+ the report body instead of `Kind:`/`Questions:`/`Answer:`.
278
+
279
+ ## Update
272
280
 
273
281
  ```bash
274
- ace-hitl overseer-send --reply-to 321 <<< "[decyzja] Rekomendacja: A."
275
- ace-hitl overseer-pending
276
- ace-hitl overseer-ack msg-0123456789abcdef
282
+ ace-hitl update abc123 --set status=in-progress
283
+ ace-hitl update abc123 --add tags=reviewed
284
+ ace-hitl update abc123 --remove tags=stale
285
+ ace-hitl update abc123 --answer "Use JWT with server-side refresh tokens."
286
+ ace-hitl update abc123 --answer "close the assignment" --resume
287
+ ace-hitl update abc123 --move-to archive
288
+ ace-hitl update abc123 --move-to next
277
289
  ```
278
290
 
279
- Responses are 1..1200 characters and must open with a type tag
280
- (`[decyzja]`, `[pytanie]`, or `[info]`); full SHAs, Work/Attempt/task
281
- IDs, or the word "SHA" are rejected. `overseer-send` is the overseer
282
- user's operation; `overseer-pending`/`overseer-ack` are host-broker
283
- (root) operations used by the transport to drain and acknowledge
284
- relayed responses.
291
+ `--answer` writes the `## Answer` body and marks the event answered. `--resume`
292
+ optionally dispatches the event's resume instructions after the answer.
285
293
 
286
- ## Wait (Polling Default)
294
+ ## Wait
287
295
 
288
- Wait only for a specific HITL id. This is the default reliability path for the requester agent.
296
+ Poll one specific event until it is answered:
289
297
 
290
298
  ```bash
291
299
  ace-hitl wait abc123
@@ -293,28 +301,12 @@ ace-hitl wait abc123 --poll-every 600 --timeout 14400
293
301
  ace-hitl wait abc123 --scope current
294
302
  ```
295
303
 
296
- Managed event waits resolve `lab_request_id` and consume through authenticated
297
- IPC. They never read folder projections or persist returned answers into the
298
- local event. Explicit pane-less scripts can wait without any local event:
299
-
300
- ```bash
301
- ace-hitl wait --request hitl001 --timeout 30
302
- ace-hitl wait --request otp001 --operation gem-push
303
- ```
304
-
305
- The default managed wait is indefinite; a positive timeout bounds only this
306
- call. It never expires the request. OTP consumption requires the authorized
307
- operation and returns its bytes only over protected IPC. Local event polling
308
- remains available for ordinary unbound local events. Managed events cannot use
309
- `update --resume` to launch unscoped session/shell delivery.
310
-
311
- Installed acceptance uses actual registered Telegram ingress, the selected
312
- live Herdr/tmux terminal and the real scoped users in Lab. Controlled local
313
- transport tests do not replace launching and observing the actual task.
304
+ The default wait is indefinite; a positive `--timeout` bounds only this call
305
+ and never expires the request. `wait` never invents an answer.
314
306
 
315
- ## Lifecycle Event Names
307
+ ## Lifecycle event names
316
308
 
317
- Canonical namespace for HITL lifecycle signaling:
309
+ HITL lifecycle signals use this namespace:
318
310
 
319
311
  - `hitl.event.created`
320
312
  - `hitl.event.answered`
@@ -325,195 +317,29 @@ Canonical namespace for HITL lifecycle signaling:
325
317
  - `hitl.event.resume_failed`
326
318
  - `hitl.event.archived`
327
319
 
328
- ## Pending recovery history across bounded IPC pages
329
-
330
- `ace-hitl pending --project ace` continues to show unresolved native delivery
331
- claims even after their answers have been consumed. A large retained history
332
- must not prevent newly created questions from reaching Telegram.
333
-
334
- The lifecycle wire `pending` operation returns `{items, next}`. `next` is an
335
- exclusive request-ID cursor; send it as `after` with the same project for the
336
- next page. Each response, including framing, fits the IPC byte limit. Every
337
- page rechecks transport/project authorization. `Lifecycle::Client#pending`
338
- collects the pages for existing Ruby/CLI callers; `pending_page(project:, after:)`
339
- exposes a single bounded page. `read(id)` remains available for exact recovery.
340
- No consumed claim is deleted to make the list fit. A record too large for one
341
- page produces a classified error, never a successful truncated list.
342
-
343
- This is a live keyset scan, not a frozen snapshot: a newly inserted ID before
344
- the current cursor appears on the next scan. Repeated polling therefore remains
345
- required; a cursor is not proof of delivery or native consumption.
346
-
347
- ## Immutable second-commander proposals
348
-
349
- `ace-hitl proposal create proposal-0123456789abcdef01234567 --assignment ID --attempt ID --project ID --file proposal.json`
350
- returns persisted immutable proposal/revision/request IDs and current awaiting-delivery state immediately, even with unavailable transport. delivered_at and deadline are absent until acknowledged submission; show exposes them afterward. The sole Hermes
351
- polling actor publishes the full precise proposal and records confirmed submission before
352
- HITL persists delivered_at and a deadline exactly sixteen hours later. Failed or uncertain
353
- submission cannot arm the window. A Telegram Reply `approve [rationale]`, `veto [rationale]`
354
- or `clarify [rationale]` applies only to the correlated immutable revision; other replies
355
- stop automatic approval and require revision. Missing rationale remains absent.
356
-
357
- The JSON proposal requires operation, target (`resource` and optional `artifact_digest`),
358
- candidate_head, input_digest, context, options, recommendation and prerequisites; rationale
359
- is optional. Contents are bounded and non-secret. Input digest uses the canonical service
360
- input digest. Candidate head is separate from base head and the evidence journal commit.
361
- Only operation classes explicitly delegated in trusted project policy are eligible
362
- for sixteen-hour silence. Delegation must exist when the revision is created and
363
- remain current at the deadline; a later grant cannot approve an older revision.
364
- Without delegation the proposal still accepts an explicit correlated decision.
365
- Fixed service scope, current head,
366
- independently executed review/tests, bound running attempt and operation-specific OTP gates
367
- still apply at execution. Proposal authorization never supplies credentials or runs a callback.
368
-
369
- All proposal reads, revisions and transport acknowledgements/replies carry an explicit project.
370
- The authority selects that project's canonical Assign journal; a proposal ID does not select a journal.
371
- The managed request's existing project supplies this selector for ordinary `read`; Hermes uses the
372
- accepted envelope's project for acknowledgement, reply and reconciliation. Missing selectors are
373
- rejected, and an ID from another project's journal cannot be read or changed through the selected project.
374
-
375
- `ace-hitl proposal show ID --project PROJECT --format json` shows decision, deadline, actual Assign claim/outcome
376
- and a bounded history page; continue with `--history-after HISTORY_NEXT`.
377
- `ace-hitl proposal history --project ID --query TEXT` retrieves relevant prior decisions;
378
- continue with `--after NEXT`. Current project grants and exact requester identity gate history and show; losing project access refuses reads and excludes history, including lifecycle proposal request reads.
379
- `ace-hitl proposal revise ID --project PROJECT --expected-revision N --operation-id revision-abcdef0123456789abcdef01 --file changed.json` supersedes the prior decision and creates a
380
- new request with a fresh full window after acknowledgement. An unresolved claimed effect
381
- must be reconciled before revision; known successful or proven no-effect settlement can be
382
- followed by a new revision. Persist the proposal ID, expected source revision, stable revision operation ID and exact file before invocation. The operation ID is `revision-` followed by 24 lowercase hex digits and is unique across proposals. Exact retry returns that committed revision, including after acknowledged delivery, approval or a later revision, without creating another request or resetting the window. Changed proposal/source/content/caller bindings and stale-source new operations are refused. Supersession and new prepared revision commit atomically against effect claims. Generic lifecycle `create` rejects proposal kind; use this canonical proposal interface.
383
-
384
- Set ACE_HITL_SOCKET and ACE_HITL_PROJECT for the living overseer. It calls
385
- `ace-hitl proposal resolve-due --project PROJECT` on start/status/watch ticks.
386
- The authenticated proposer queues an idempotent reconciliation wake in the
387
- canonical Assign proposal; the command returns `queued-for-transport`, never an
388
- approval claim. The existing installed Hermes `serve` loop polls Telegram under
389
- its own configured transport UID and reconciles queued deadlines after polling.
390
- No proposer subprocess opens transport configuration or impersonates transport.
391
- Hermes holds its ingress lock across checkpoint and HITL decision transition;
392
- unknown health/backlog defers resolution. Failed ticks remain visible while
393
- watch/status continues. Production `--now` is rejected.
394
-
395
- Creation requires an explicit stable ID (`proposal-` followed by 24 lowercase
396
- hex digits). Persist that ID before invocation and retry the exact same ID,
397
- assignment, attempt, caller and document after failure or a lost reply. Changed
398
- binding/content is refused. Assign commits the immutable prepared lifecycle
399
- request first; exact retry or the transport pending scan materializes it after
400
- restart. No pending lifecycle orphan exists before canonical commit. Concurrent
401
- materialization creates once and preserves current delivered/answered state.
402
-
403
- Unresolved earlier same-request ingress blocks later approval delivery. Exact
404
- reply sequence/content/time deduplication uses canonical decision history;
405
- unseen lower sequence is applied, and changed duplicate content is refused.
406
- History grows in the existing canonical event chain, while public responses use
407
- bounded pages. Hermes metadata never stores message bodies.
408
-
409
- The immutable authorization reference is the revision ID, passed to `ace-lab service request`.
410
- Assign atomically checks the canonical proposal under its sole journal claim lock/CAS;
411
- a forged prefix/YAML string or changed operation/target/head/input/caller cannot authorize.
412
- A late veto before claim denies execution. After claim it records stop_requested without
413
- rewriting performed effects; the service rechecks it before safely stoppable invocation.
414
- An uncertain claim remains uncertain on restart and is never automatically dispatched again.
415
- Canonical sanitized decision history shares the qjl evidence ref; no parallel executor or
416
- execution ledger exists in HITL. This source workflow is not installed/native/Telegram or
417
- multi-UID acceptance proof.
418
-
419
- ### Proposal admission configuration
420
-
421
- The protected HITL service reads proposer admission from the same trusted grants
422
- policy as transport and project authorization. For example:
320
+ ## Transport boundary
321
+
322
+ The installed Lab provider and the attempt-bound managed HITL path (`ask`, the
323
+ provider assignment binding, native Herdr delivery, managed/TLS `serve`) were
324
+ removed with the old authority model. The local file-based event commands above
325
+ remain the active path. The generic lifecycle/relay transport (`Lifecycle::*`)
326
+ and its commands (read/pending/states/duty/deliver/consume/cancel) remain for
327
+ adapters such as `ace-hitl-hermes`; `serve` supports `--operator-only` only.
328
+ The immutable second-commander proposal flow is removed, and so is the unused
329
+ old-lab Root Overseer reverse-address channel (`overseer-*` commands); generic
330
+ transport has no replacement role-specific queue.
331
+
332
+ > **L2 status:** the report/question distinction, recipient addressing
333
+ > (`--to`, `reply_to`), adapter-route delivery (`send` with the built-in
334
+ > terminal adapter and `ace-hitl-hermes` as the hermes adapter), and
335
+ > retention/redelivery of undelivered messages (`send` retry/`--force`,
336
+ > `list --delivery`, per-route `Delivery:` records) are available. Adapters
337
+ > (`ace-hitl-hermes`) and transports (tmux, Herdr) are not additional state
338
+ > owners.
423
339
 
424
- ```yaml
425
- hitl:
426
- service_uid: 1200
427
- transport_uids: [1201]
428
- proposal_uids: [1202]
429
- silence_operations:
430
- ace: [merge]
431
- authorization:
432
- principals:
433
- "1201":
434
- projects: [ace]
435
- "1202":
436
- projects: [ace]
437
- ```
438
-
439
- `proposal_uids` admits authenticated kernel peers to create and revise proposals
440
- only for their configured projects. Transport admission alone cannot create a
441
- proposal; proposer admission cannot acknowledge delivery, fabricate ingress, or
442
- resolve silence. Missing role or project admission refuses the operation,
443
- including direct library calls. This is a source configuration contract;
444
- installed deployment adoption remains part of gad.8 acceptance.
445
-
446
- `silence_operations` is an exact project-to-operation list, with no wildcards or
447
- implicit publish/deploy/access grants. It never expands command or credential
448
- authority. TLS authorities read the same trusted grants document selected by
449
- `--grants-path`; certificate authentication alone grants no silence decision.
450
-
451
- ### Controlled installed proposal verification
452
-
453
- From an ACE checkout with Ruby, tmux and the complete dependency archives cached locally:
340
+ ## Testing
454
341
 
455
342
  ```bash
456
- bin/ace-test ace-hitl edge --filter installed_proposal_test --config-path "$PWD/ace-hitl/test/support/installed_proposal/runner.yml"
457
- ```
458
-
459
- The explicit fixture configuration activates the installed scenario without changing
460
- test deadlines. Default package runs skip it. The run builds the current runtime
461
- gem closure and installs it into an empty GEM_HOME, using local archives only.
462
- It prints `Installed SC3 artifacts: /tmp/ace-installed-sc3/run-...`; read the
463
- reported test receipt and that directory's `result.json`, `artifacts.json`,
464
- `build-provenance.json`, `processes.jsonl` and `canonical-evidence.json`. A manifest/result pointer is
465
- retained under checkout `.ace-local/installed-sc3/`.
466
-
467
- The expected terminal result is one passing test with no failures/errors. Its
468
- consumer asserts confirmed submission + sixteen hours, actual service/actor
469
- process restarts, one effective authorization and one executor invocation with
470
- a canonical verified receipt. Failed submission does not arm a deadline; lost
471
- poll coverage stays blocked, including after fresh polling. Changed technical
472
- scope remains refused after approval. No inbox record is manually inserted.
473
-
474
- UTC and Telegram are test-only injected adapters. Assignment binding, kernel peer
475
- authentication, policy, claim, execution and receipt verification remain real.
476
- The fixture uses one host and the current UID, with an isolated tmux process and
477
- the existing local service mode. It does not prove live Telegram, protected Herdr
478
- launch, installed root-owned grants, or multiUID privilege separation. There is
479
- no production `--now` or transport-override option.
480
-
481
- Missing local dependency archives fail visibly without network fallback. A failed
482
- consumer retains diagnostic artifacts; inspect `result.json` and `service.log`
483
- before retrying. Each invocation retains a new run directory and requires a new
484
- empty GEM_HOME.
485
-
486
- ## The host/Incus mTLS crossing (nh0-A)
487
-
488
- `ace-hitl serve --tls-authority FILE` runs one lifecycle authority of
489
- the shared-Hermes topology on a loopback TCP endpoint where the
490
- project's Incus TCP proxy device connects. The authority config
491
- (`ace.hitl.tls.authority/v1`, closed fields, root-owned runtime file
492
- `root:<service-group> 0640`) owns the loopback listen address, the
493
- project ID, the service account names and the server credential
494
- reference — never secret bytes:
495
-
496
- ```json
497
- {"schema": "ace.hitl.tls.authority/v1",
498
- "project_id": "proj-a", "listen": "127.0.0.1", "port": 8443,
499
- "service_user": "hitl-proj-a", "service_group": "hitl-proj-a",
500
- "tls_server_credential": {
501
- "certificate_file": "/etc/ace-hitl/proj-a/server.pem",
502
- "private_key_file": "/etc/ace-hitl/proj-a/server-key.pem",
503
- "ca_file": "/etc/ace-hitl/proj-a/ca.pem",
504
- "server_uri_san": "urn:ace:hitl:proj-a:lifecycle-server",
505
- "allowed_client_uri_san": "urn:ace:hitl:proj-a:hermes-client",
506
- "allowed_client_certificate_sha256": "<64 lowercase hex>"}}
343
+ bin/ace-test ace-hitl
344
+ bin/ace-test ace-hitl all
507
345
  ```
508
-
509
- Startup enforces the exact provisioning layout (ancestors `root:root`
510
- 0755, project directory `root:<service-group>` 0750, private key
511
- `<service-user>:<service-group>` 0400 and never hard-linked,
512
- certificate/CA `root:root` 0444) BEFORE any socket opens, validates its
513
- own certificate identity (single URI SAN, serverAuth EKU, key match),
514
- speaks TLS 1.3 only, and admits exactly the configured client
515
- SAN/fingerprint pair — bound to the authority's one project, with
516
- pending flows and creates refused for any other project. Unix
517
- peer-credential serving (`serve` without `--tls-authority`) remains the
518
- same-runtime boundary. lab-config (gad.8/gad.b) owns accounts, file
519
- provisioning, Incus proxy devices and the tailnet listeners.