ace-hitl 0.17.0 → 0.23.1

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 +146 -0
  3. data/README.md +60 -117
  4. data/docs/usage.md +237 -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 +88 -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 +78 -0
  40. data/lib/ace/hitl/organisms/hitl_manager.rb +51 -5
  41. data/lib/ace/hitl/organisms/hitl_sender.rb +147 -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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8ab176a502d7a751a22e89f461245046caa44d71943897b7f27b6d5ffdd10c84
4
- data.tar.gz: f789375f072b07f087462b78fd2d2fb7a9ae45e167eedbd413a11fe09e9ead0e
3
+ metadata.gz: a9186e91db61d509bafd0c44d8ad023ed208614ad8cbfe749d3b49b0bbf10772
4
+ data.tar.gz: 151a5ad5cda6dc92256a7f2f604249858c04666c23a5a6ce85c9347dbf4640f4
5
5
  SHA512:
6
- metadata.gz: b6f1c00953d56deac3fe8e7099a1aac527681b1314aeda336c7691de04bf7ab40af33c8815ddcac76777e02151609c555cb0f4f0be2276e8a7b8ab0359b3a9ae
7
- data.tar.gz: 19e978992b0041ae887a26d743555fe69b8292fd01c763c488bef96e6feecb00c3437f58909d015eeaefa21884b552b203af47f94e583b2feef4e8bc3f4e06fc
6
+ metadata.gz: 8f6a6ef440d4404df3868ddc1e416032a09a1b59eb36e9d9a9317f214939677a5c58a724ec8cebd10bf88a7d5d1f412671f724be0957dd54c938b54cf06a1c44
7
+ data.tar.gz: 6dd4dae6d026e1167dcb7225188f7b5e73fd9fbfc4fde1625e1988daf4fb4922b2e8a477e5f142c68aad6032a806512fc6506ab0ad05eb71aed144627965e137
data/CHANGELOG.md CHANGED
@@ -7,6 +7,152 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.23.1] - 2026-10-18
11
+
12
+ ### Fixed
13
+
14
+ - Terminal routes are machine-scoped (round-9 review H1): the adapter now
15
+ reads the route's `machine` (defaults to `hitl.machine`) and refuses a
16
+ panel recipient on a different machine instead of delivering to a
17
+ local pane that merely shares the panel name and reporting a false
18
+ `delivered`. The refusal mirrors the hermes adapter, so both shipped
19
+ adapters apply the same address check.
20
+
21
+ ### Changed
22
+
23
+ - Docs only: `docs/usage.md` now documents that route delivery requires
24
+ a panel-form recipient (folder-form recipients have no shipped delivery
25
+ route and are refused per-route), the per-route refusal semantics of
26
+ both shipped adapters, per-route `unavailable` retention for unknown
27
+ adapters, dotted route-name rejection, and the absolute `reply_to`
28
+ folder. `HitlConfigLoader.normalize` notes that mixing flat/shaped
29
+ config documents across tiers can invert cascade precedence.
30
+
31
+ ## [0.23.0] - 2026-10-18
32
+
33
+ ### Fixed
34
+
35
+ - Adapters deliver to the event's recipient, not just the route's own
36
+ destination (PR #383 audit F1): the terminal adapter sends to the
37
+ recipient's `panel` (the route `pane` remains the fallback only for
38
+ legacy recipient-less events) and explicitly refuses a folder-form
39
+ recipient it cannot serve — never a silent send to the route pane.
40
+ Flat hitl config documents are no longer silently dropped (F11):
41
+ `.ace/hitl/config.yml` without the `hitl:` key now merges over the
42
+ shipped defaults instead of being shadowed by them. The default reply
43
+ address is built from the resolved sender folder (F6): an absolute
44
+ path that identifies the originating worktree, so two worktrees on
45
+ one machine never share a reply address (the relative `root_dir`
46
+ setting stays relative in config). Route names containing a dot
47
+ (e.g. `captain.telegram`) are rejected before any delivery attempt
48
+ (F10): delivery retention keys route names literally and a dot would
49
+ nest frontmatter keys, breaking retry/skip — `send` now fails closed
50
+ with `InvalidRouteError` naming the route.
51
+
52
+ ### Changed
53
+
54
+ - An unknown adapter on one route no longer aborts the whole send
55
+ (PR #383 audit F5): the route is retained as `unavailable` with the
56
+ resolution error as its reason (never a bare `in_flight` marker), the
57
+ remaining routes still deliver, and the send still reports overall
58
+ delivery failure.
59
+
60
+ ## [0.22.0] - 2026-10-18
61
+
62
+ ### Added
63
+
64
+ - Retention and redelivery of undelivered messages (8xg.t.sbz.1.3 / L2.4):
65
+ `send` now persists each route's last outcome, failure reason, and attempt
66
+ timestamp on the event's own file (`delivery.<route>` frontmatter — the
67
+ single state owner per ADR-034; no journal or queue). Default `send`
68
+ retries only undelivered or unknown-result routes and skips confirmed
69
+ delivered ones; `--force` redelivers every route and `--dry-run` reports
70
+ the retry/skip decisions without delivering. A `in_flight` marker written
71
+ before each attempt means a crashed send leaves an unknown result that the
72
+ next `send` retries — at-least-once semantics: a retry may duplicate, and
73
+ every rendering carries the same `(case <ID>)` + task binding so
74
+ recipients dedup by ID (exactly-once is not promised). Redelivery retries
75
+ the delivery, never the assignment. `list --delivery undelivered|delivered`
76
+ discovers retained events; `show` prints a per-route `Delivery:` block
77
+ with the retained failure reason. Redelivering an all-delivered event is a
78
+ no-op success (`nothing to retry (use --force to redeliver)`).
79
+
80
+ ## [0.21.0] - 2026-10-18
81
+
82
+ ### Added
83
+
84
+ - Configurable adapter delivery routes (8xg.t.sbz.1.2 / L2.3): `ace-hitl
85
+ send <ref>` delivers a persisted event through the routes configured under
86
+ `hitl.adapters.routes` — the sender interface is adapter-independent, so
87
+ switching transports is pure configuration (no assignment or coworker
88
+ change). Every route reports its own result line; one route's success never
89
+ hides another's failure and an unavailable adapter never silently reroutes
90
+ (per-route isolation, fail-closed unknown-adapter/no-route errors).
91
+ Built-in `terminal` adapter pushes the shared event text to a pane via any
92
+ `ace-runtime` terminal (tmux/herdr); `ace-hitl-hermes` 0.8.0 ships the
93
+ `hermes` adapter entrypoint (publishes the shared rendering to the channel
94
+ folder; identical resend is idempotent, different-bytes collision fails
95
+ without overwrite, missing folder is `unavailable`). New adapters register
96
+ through the duck-typed `Ace::Hitl::Adapters` registry
97
+ (`lib/ace/hitl/adapters/<name>.rb` entrypoint — a future Nervus adapter
98
+ needs no ace-hitl change). Shared rendering (`Atoms::HitlEventText`):
99
+ `[HITL question]`/`[HITL report <result>]` prefix, `Reply-To:` on questions
100
+ only, task/assignment/step context. Delivery results are reported, not
101
+ persisted — retention and redelivery of undelivered messages is
102
+ 8xg.t.sbz.1.3.
103
+
104
+ ## [0.20.0] - 2026-10-18
105
+
106
+ ### Added
107
+
108
+ - Recipient addressing and Reply binding (8xg.t.sbz.1.1 / L2.2): `ace-hitl
109
+ create ... --to <name>` resolves a configured recipient into an explicit
110
+ address — machine + events folder + case ID (folder form, ID stamped from
111
+ the new event) or machine + panel — read from the `hitl` cascade
112
+ (`machine`, `default_recipient`, `recipients:<name>:{machine,folder|panel}`).
113
+ Questions record a `reply_to` address (sender machine + events folder + own
114
+ case ID) so answers bind to the original case and the right agent;
115
+ task/assignment/step stay context, never an ID substitute. Local Reply is
116
+ unchanged: `update <ref> --answer` bound by exact ID with strict ambiguity
117
+ and explicit not-found — no write to a foreign event. Unknown recipient
118
+ names, malformed configured addresses, and a missing `hitl.machine` fail
119
+ closed before anything is written. `show`/create output print
120
+ `Recipient:`/`Reply-To:`; `HitlEvent#recipient`/`#reply_to` expose the
121
+ address hashes for adapters (delivery itself lands in 8xg.t.sbz.1.2/.1.3;
122
+ local write is not delivery).
123
+
124
+ ## [0.19.0] - 2026-10-18
125
+
126
+ ### Fixed (review PR #383, round 3)
127
+
128
+ - Report events carry no `kind` (loader returns nil), so question-oriented
129
+ `list --kind` filters never mix reports into a clarification list.
130
+
131
+ ### Added
132
+
133
+ - Report events: the same `ace-hitl` file interface now records work-done/error notifications alongside questions. `ace-hitl create "..." --type report --result success|error [--body line]... [--task REF]` writes an event with status `recorded`, a `## Report` body, task/assignment/step references, and its own ID. Reports never await an answer: `wait`, `update --answer`, and `update --resume` on a report fail fast with an explicit error (exit 1); generic `--set/--add/--remove/--move-to` still work so an overseer can archive processed reports.
134
+ - `--type question` is now written explicitly on new question events (loader defaults keep existing files without `type` valid questions); `--task` is accepted for both types; `list --type question|report` filters by type; `show` prints `Type:`/`Result:` and list gains the `recorded` status symbol.
135
+ - Library guards: `HitlManager#update(answer:)` raises `NotAnswerableError` on reports before any file mutation; `wait_for_answer` and `dispatch_resume` return `{status: :report}` without sleeping.
136
+
137
+ ### Notes
138
+
139
+ - Local write is not delivery: `recorded` means the report is persisted in `.ace-local/hitl/`, not that it reached the overseer. Remote addressing, reply routing, and configurable adapters land in 8xg.t.sbz.1.1–.1.3.
140
+
141
+ ## [0.18.0] - 2026-10-18
142
+
143
+ ### Removed
144
+
145
+ - Remove the installed Lab provider and its attempt-bound managed HITL path: `Providers::Lab::AssignmentBinding`, `ProtectedAssignmentBinding`, the provider `ask`/native-delivery methods, the `ask` CLI command, and the Herdr-native delivery client in `LiveClient` (the old ace-assign `AttemptCoordinator`/journal identity).
146
+ - Remove the second-commander proposal surface: the `proposal` CLI command, `Lifecycle::Proposals`, the proposal policy/evaluator, proposal frames, and the publication HITL binding tied to the removed publication authority.
147
+ - Remove managed and TLS `serve` modes: `serve` requires `--operator-only` and fails clearly otherwise.
148
+ - Remove the unused old-lab Root Overseer reverse-address channel: `Lifecycle::Overseer`, the `overseer-send`/`overseer-pending`/`overseer-ack` CLI commands, the default `mo` responder, the `ACE_HITL_OVERSEER_CHANNEL_ROOT`/`/lab/state/overseer-channel` default, and the `[decyzja]`/`[pytanie]`/`[info]` response policy. Generic HITL transport and the ready lifecycle surface are unchanged; there is no replacement queue or relay for this role policy.
149
+ - Remove the now-dead `LifecycleCommand::STDIN_READER` constant (`deliver` reads stdin inline) and add a Duty projection regression test covering the answer-delivered pending state and mixed effect visibility.
150
+
151
+ ### Changed
152
+
153
+ - `LiveClient` keeps the requester-side authenticated boundary operations (`wait`, `status`, `pending`); the lifecycle/relay transport (`Lifecycle::*`) remains for `ace-hitl-hermes`.
154
+ - Drop the now-unused `ace-assign` and `ace-herdr` runtime dependencies (the managed attempt binding required them); re-lock the workspace Gemfile.lock locally.
155
+
10
156
  ## [0.17.0] - 2026-10-16
11
157
 
12
158
  ### Added
data/README.md CHANGED
@@ -1,137 +1,80 @@
1
1
  # ace-hitl
2
2
 
3
- `ace-hitl` manages ACE human-in-the-loop (HITL) events in `.ace-local/hitl/`.
3
+ Human-in-the-loop (HITL) event files for ACE agents and operators.
4
+
5
+ > **State owner:** `.ace-local/hitl/` -- HITL event markdown files owned by
6
+ > `ace-hitl`. Other tools must not write there directly. The agent event
7
+ > history is local and untracked. The canonical flow is
8
+ > [docs/lab-flow.md](../docs/lab-flow.md).
4
9
 
5
10
  Canonical workflow and skill for agents:
6
11
 
7
12
  - Workflow: `wfi://hitl`
8
13
  - Skill: `as-hitl`
9
14
 
10
- ## Commands
11
-
12
- - `ace-hitl create` creates a HITL event
13
- - `ace-hitl ask` asks a human via HITL and forwards the request through a provider adapter (`--provider`, default `lab`); ONE operation: local event + relay request through the authenticated boundary (`--assignment/--attempt/--project` managed binding, effect callback flags)
14
- - `ace-hitl list` lists HITL events with filters (`--scope current|all`, all statuses by default)
15
- - `ace-hitl show` renders event details, path, or raw content (`--scope current|all`)
16
- - `ace-hitl update` updates frontmatter, answer content, and folder location
17
- - `ace-hitl wait` polls a specific HITL event until answered (`--poll-every`, `--timeout`)
18
- - `ace-hitl deliver` answers a pending relay request from stdin (configured transport operation through the boundary); executes the declared effect callback (non-OTP kinds)
19
- - `ace-hitl consume` consumes one own relay request's answer (indefinite by default; `--timeout` bounds only the local wait)
20
- - `ace-hitl cancel` cancels with an audited reason (the only way to abandon a request)
21
- - `ace-hitl pending` / `ace-hitl states` / `ace-hitl duty` transport projections (pending, public lifecycle records, pending + escalated)
22
- - `ace-hitl serve` runs the authenticated store boundary service (peer-credential UNIX socket; trusted grants authorize the transport)
23
-
24
- Multi-user HITL state (spec 8wq.t.34i) is the PRIVATE state of the
25
- `ace-hitl serve` boundary: identity comes from kernel peer
26
- credentials, transport authority from the trusted grants document,
27
- and OTP secrets are held in service memory only — never in files,
28
- logs, or projections.
29
- - `ace-hitl overseer-send` / `ace-hitl overseer-pending` / `ace-hitl overseer-ack` the Overseer reverse-address response channel
30
-
31
- `ace-hitl` is a blocker-resolution tool, not a global dashboard:
32
-
33
- - linked worktree default: local (`--scope current`)
34
- - main checkout default: operator view (`--scope all`)
35
-
36
- Use `ace-overseer status` for a global worktree dashboard.
37
-
38
- ## Provider adapters
39
-
40
- `ace-hitl ask --question ... --assignment ... --attempt ...` creates a scoped
41
- request through authenticated IPC. The managed coordinator verifies the exact
42
- active native owner and reverse address; caller environment variables cannot
43
- supply authority. No Lab daemon or Work binding is used.
44
-
45
- `Ace::Hitl::LiveClient` provides explicit in-process `deliver`, `watch`, `wait`,
46
- `status`, `pending`, and signed `reconcile`. `watch` returns a thread owned by
47
- its calling agent. Native delivery commits an incarnation-bound event through
48
- Herdr Inbox and registers it through Assign. Queue acceptance and wake do not
49
- prove consumption. Only an event-bound signed native observation verified by
50
- Herdr and journaled by Assign completes delivery. Business callbacks have
51
- separate authorization and receipts and are never rerun by native retries.
52
-
53
- Pane-less `wait --request ID` consumes an existing authorized request over IPC;
54
- it neither creates a native target nor proves a business effect. OTP answers
55
- use that protected path with their authorized operation; no OTP bytes or OTP
56
- hash enter the shared envelope, folder or native inbox.
15
+ ## What exists today
16
+
17
+ `ace-hitl` manages HITL events stored as markdown files under `.ace-local/hitl/`.
18
+ An event records a question or decision, its context (assignment/step), its
19
+ status, and the answer — or a report (work done / error) that never awaits an
20
+ answer. This is the local, file-based path; it needs no
21
+ listener, daemon, or database.
22
+
23
+ | Command | Purpose |
24
+ |---------|---------|
25
+ | `ace-hitl create` | Create a question event (kind, questions, assignment/step context, resume instructions) or a report event (`--type report --result success\|error --body ... --task ...`) |
26
+ | `ace-hitl list` | List events with status/kind/type/tag/folder/scope filters |
27
+ | `ace-hitl show` | Render event details, path, or raw markdown |
28
+ | `ace-hitl update` | Update frontmatter, write an answer, or move the event |
29
+ | `ace-hitl wait` | Poll one question event until it is answered (reports are rejected) |
30
+ | `ace-hitl send` | Deliver an event through the configured adapter routes; per-route results retained on the event, redelivery retries undelivered/unknown routes |
31
+
32
+ `ace-hitl` is a blocker-resolution tool, not a global dashboard: in a linked
33
+ worktree it defaults to local (`--scope current`); in the main checkout it uses
34
+ the operator view (`--scope all`). Use `ace-overseer status` for a worktree
35
+ dashboard.
57
36
 
58
37
  ## Examples
59
38
 
60
39
  ```bash
61
- ace-hitl list
62
- ace-hitl list --scope all
63
- ace-hitl create "Which auth strategy?" --kind decision --question "JWT or sessions?"
64
- ace-hitl ask --question "Proceed with deploy?" --assignment assign685 --attempt attempt685 --effect-arg /bin/false --effect-cwd /tmp
65
- ace-hitl ask --question "Proceed with deploy?" --provider lab --assignment assign685 --attempt attempt685
40
+ ace-hitl create "Which auth strategy?" --kind decision \
41
+ --question "JWT or sessions?" --assignment abc123 --step 020
42
+ ace-hitl create "Work done" --type report --result success \
43
+ --body "Implemented JWT login" --task 8xg.t.sbz.1.0
44
+ ace-hitl list --status pending
45
+ ace-hitl list --type report
66
46
  ace-hitl show abc123 --content
67
- ace-hitl show abc123 --scope current
68
47
  ace-hitl update abc123 --answer "Use JWT with server-side refresh tokens."
69
- ace-hitl wait abc123
70
- ace-hitl update abc123 --answer "Use JWT with server-side refresh tokens." --resume
48
+ ace-hitl update abc123 --move-to archive
49
+ ace-hitl wait abc123 --poll-every 600 --timeout 14400
71
50
  ```
72
51
 
73
- ## Testing
74
-
75
- This package has deterministic fast and scoped integration tests.
76
-
77
- - Deterministic test coverage lives under `test/fast/`.
78
- - This migration does not introduce `test/feat/` or `test/e2e/` for this package.
79
-
80
- Verification commands:
81
-
82
- - `ace-test ace-hitl`
83
- - `ace-test ace-hitl all`
52
+ ## Transport boundary
53
+
54
+ > The installed Lab provider and the attempt-bound managed HITL path (`ask`,
55
+ > provider assignment binding, native Herdr delivery, managed/TLS `serve`) were
56
+ > removed with the old authority model. The local file-based HITL events above
57
+ > remain the active path. The lifecycle/relay transport (`Lifecycle::*`) and
58
+ > its commands remain for `ace-hitl-hermes` and other adapters; `serve` supports
59
+ > `--operator-only` only. Second-commander proposals are removed as well.
60
+ >
61
+ > **L2 status:** report/question distinction, recipient addressing
62
+ > (`--to`, `reply_to`), adapter-route delivery (`send` with the built-in
63
+ > terminal adapter and `ace-hitl-hermes` as the hermes adapter), and
64
+ > retention/redelivery of undelivered messages (per-route records on the
65
+ > event; `send` retries undelivered/unknown routes, `--force` redelivers
66
+ > with the same case ID) are available. `ace-hitl-hermes` remains an
67
+ > adapter package; tmux/Herdr remain transports.
84
68
 
85
- ## Ownership Boundary
86
-
87
- `ace-hitl` owns HITL-specific event semantics and markdown contract.
88
-
89
- `ace-support-items` remains generic support infrastructure and should not absorb HITL-specific domain behavior.
90
-
91
- ## Unassigned operator questions
92
-
93
- `ace-hitl operator-question ID --project lab-config --question "Which option?"`
94
- creates a nonsecret question under an explicit installed operator grant. An
95
- optional `--plan` adds question context; `--options A,B` supplies bounded choices.
96
- The owner binds the exact Captain-visible body including this context and choices. It does not create an Assign execution,
97
- request a command, grant a decision, or permit OTP/effects. Stable IDs support
98
- exact retries after lost responses, restart and consumption; changed content,
99
- principal, project or context is refused. `ace-hitl read ID` returns authenticated
100
- nonsecret facts; existing `consume ID` retrieves the requester's answer and
101
- replays its terminal receipt.
102
-
103
- The public Ruby client exposes `operator_question(id:, project:, question:,
104
- plan: "", options: [])`, `read`, and the existing pending/deliver/consume/cancel
105
- methods. The request records the actual kernel peer UID/name. No caller can supply
106
- requester attribution, assignment, attempt, OTP, effect or publication fields.
107
- Secret-shaped questions, context, choices and answers are refused.
108
-
109
- An operator-only service uses the existing owner and Unix protocol:
69
+ ## Testing
110
70
 
111
- ```sh
112
- ace-hitl serve --operator-only --group ace-hitl-project \
113
- --socket /run/ace-hitl-project/lifecycle.sock --store-root /var/lib/ace-hitl-project \
114
- --grants-path /etc/ace-hitl-project/authorization.yml
71
+ ```bash
72
+ bin/ace-test ace-hitl
73
+ bin/ace-test ace-hitl all
115
74
  ```
116
75
 
117
- The root-controlled grants document must explicitly name `hitl.service_uid`,
118
- `hitl.operator_uids`, `hitl.transport_uids` and each UID's exact project list in
119
- `authorization.principals`. Operator grants never interpret an empty project list
120
- as all projects. Existing trusted-file ownership/traversal checks apply. The
121
- operator-only binding refuses managed requests and requires no Assign journal.
122
- Admission and requester grants are checked again on reads, answer transitions,
123
- retries and terminal replay; transport delivery retains its independent project
124
- grant. Revocation preserves request and terminal history. Operator questions
125
- cannot be consumed as native execution or authorized-operation claims.
126
-
127
- `ping` advertises the supported `request_contexts` (`managed` and/or
128
- `operator-question`), not a grant to create or execute work. Hermes project
129
- routes may require `request_contexts: ["operator-question"]` at startup. The
130
- shared contract uses a closed tagged union of managed execution requests and
131
- operator questions; neither missing IDs nor an unknown schema select a fallback.
132
-
133
- One Unix UID with both roles is one OS trust domain. Use a distinct transport
134
- principal where Captain reply isolation is required. Source tests using one UID
135
- for both roles do not prove that isolation. Domain adapters must separately
136
- preserve legacy unresolved host questions and prove the installed group handoff;
137
- this API never migrates or deletes that legacy history.
76
+ ## Ownership boundary
77
+
78
+ `ace-hitl` owns HITL event semantics and the markdown contract.
79
+ `ace-support-items` remains generic support infrastructure and does not absorb
80
+ HITL-specific behaviour.