ace-lab 0.7.0 → 0.9.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 (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/docs/usage.md +343 -0
  4. data/lib/ace/lab/cli/commands/assignment.rb +92 -0
  5. data/lib/ace/lab/cli/commands/command_execute.rb +28 -0
  6. data/lib/ace/lab/cli/commands/run.rb +65 -0
  7. data/lib/ace/lab/cli/commands/service.rb +12 -28
  8. data/lib/ace/lab/cli.rb +16 -1
  9. data/lib/ace/lab/molecules/command_owner_admission.rb +183 -0
  10. data/lib/ace/lab/molecules/command_policy.rb +158 -0
  11. data/lib/ace/lab/molecules/grant_resolver.rb +15 -0
  12. data/lib/ace/lab/molecules/ordinary_admission_resolver.rb +20 -1
  13. data/lib/ace/lab/molecules/ordinary_listener.rb +41 -17
  14. data/lib/ace/lab/molecules/ordinary_merge_read.rb +45 -0
  15. data/lib/ace/lab/molecules/ordinary_native_read.rb +106 -0
  16. data/lib/ace/lab/molecules/ordinary_owner_admission.rb +152 -0
  17. data/lib/ace/lab/molecules/ordinary_owner_config.rb +232 -0
  18. data/lib/ace/lab/molecules/ordinary_receiver_config.rb +12 -4
  19. data/lib/ace/lab/molecules/ordinary_socket.rb +6 -9
  20. data/lib/ace/lab/molecules/ordinary_worktree_read.rb +216 -0
  21. data/lib/ace/lab/molecules/service_executor.rb +9 -39
  22. data/lib/ace/lab/molecules/service_policy.rb +15 -4
  23. data/lib/ace/lab/organisms/authority_composition.rb +1 -1
  24. data/lib/ace/lab/organisms/command_executor.rb +220 -0
  25. data/lib/ace/lab/organisms/command_run_client.rb +131 -0
  26. data/lib/ace/lab/organisms/ordinary_assignment_client.rb +120 -0
  27. data/lib/ace/lab/organisms/ordinary_authority_composition.rb +153 -0
  28. data/lib/ace/lab/organisms/ordinary_merge_receiver.rb +77 -61
  29. data/lib/ace/lab/organisms/ordinary_merge_request.rb +41 -7
  30. data/lib/ace/lab/version.rb +1 -1
  31. metadata +33 -6
  32. data/lib/ace/lab/molecules/ordinary_ff_merge.rb +0 -86
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3799161a4575055b78e316deb18a4fa952fb1d84fa9fb10cd6e5c081a36acee
4
- data.tar.gz: dd2d0fa5764e5bce423396e93e4fd1751fddd880649219d6a130306d0ec49667
3
+ metadata.gz: a7d3d64373a6eaadb24dde7d501cd5c4b8e2ebfb370e054334297fc0796a8c9c
4
+ data.tar.gz: 98961e2da9d0b7383cb48a3ae3a801b7018a21c8822fa95fde779c94f2ddfb87
5
5
  SHA512:
6
- metadata.gz: 1556efc67f0fa45a412b40430570355578c7e61000bda7af07982345631f023c3f56c709d2d02a25c3faf84495b510d6fb171a904d8f910e7c336b4b8113cd44
7
- data.tar.gz: a78076debb811ce94046ff8cb056648b6455f8dbe30514a5d575ac417bcf98adce21bb42c3f6f712f0d0e2d6d4bbb3c67ba3764dfb9e4138cb6eb17e6dd6a113
6
+ metadata.gz: fae21d841c34b8ff24586a806778377589a476ea4e824e9716c6ff55700d207fc9a501656fe2d1712c5c8358a29e22a31a2c8fb2cde8713d464e700f6fdea795
7
+ data.tar.gz: feacdf38956ff9a1c3afe9296e4cc762deba5029ece4dbc6e9d6bd3a1d6e28e825d746e29cd0f52268c39ba5fe4bd5d97a48e93d7a8f901da38ad9225db41ffc
data/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.9.0] - 2026-10-16
11
+
12
+ ### Added
13
+
14
+ - Wire ordinary artifact retention to fresh installed project visibility and the existing exact service or canonical proposal decision; retention does not select an executor or consume publication authorization.
15
+ - Expose ordinary candidate upload, assigned review export/acceptance and canonical merge evidence through retained assignment requests. Compose their proof owner with the existing merge receiver and command executor journal.
16
+ - Expose ordinary driver status, bootstrap, progress, result and exact recovery commands using the selected Assign owner; authenticate coordinator replacement with bounded process-generation observation.
17
+
18
+ ### Changed
19
+
20
+ - Require an explicit `purpose: command` or `purpose: test` on every installed command-policy entry. Only test commands with exact candidate head/tree and canonical successful execution can authorize merge evidence; previous entries without purpose are refused.
21
+
22
+ ## [0.8.0] - 2026-10-16
23
+
24
+ ### Added
25
+
26
+ - Compose ordinary Assign, HITL and Forgejo merge under one installed owner and journal, with readiness barriers and fresh native/project admission.
27
+ - Optionally serve existing mTLS HITL beside the ordinary Unix listener under the same canonical owner, journal, proposal policy and one-use OTP vault. Validate the configured project and service identity before either listener starts.
28
+ - Add retained assignment prepare/request clients and bounded canonical worktree observations. Separate stable configuration identity from changing decisions, preserve no-effect settlement after slow provider calls, and clean delivery handler process groups.
29
+ - This source cut enables initial installation; it does not claim autonomous release or installed two-project acceptance.
30
+
10
31
  ## [0.7.0] - 2026-10-16
11
32
 
12
33
  ### Added
data/docs/usage.md CHANGED
@@ -121,6 +121,298 @@ Only the original OS caller identity with project visibility can read the
121
121
  request. The authoritative state survives loss of the local assignment cache
122
122
  because it lives in the separate assignment evidence Git ref.
123
123
 
124
+ ### `ace-lab service listen --mode ordinary-merge`
125
+
126
+ The installed ordinary service owns the Assign, HITL and merge endpoints in one
127
+ process. Start it as the configured service account:
128
+
129
+ ```sh
130
+ ace-lab service listen --mode ordinary-merge --project atlas \
131
+ --config /etc/lab/ace-lab/atlas-receiver.yml
132
+ ```
133
+
134
+ The receiver configuration selects its existing forge, repository, principal and
135
+ merge socket. `/etc/lab/ace-lab/assign-owners.yml` separately selects the canonical
136
+ Assign journal and native execution owner. Both selections must agree with the
137
+ ordinary project mapping in the trusted authorization document. `repo_path` is
138
+ the merge candidate repository; it does not choose the execution journal.
139
+
140
+ Each project entry in the root-installed assignment document contains:
141
+
142
+ | Entry | Installed selection |
143
+ |---|---|
144
+ | `service` | Non-root owner UID/GID, assignment socket and private state root |
145
+ | `journal` | Service-owned repository, execution ref and private evidence checkout |
146
+ | `assignment_root`, `lifecycle_root` | Separate private prepared-input and exclusion directories |
147
+ | `runtime_binary`, `native` | Installed executable and exact native server/socket generation from the installation receipt |
148
+ | `worktrees` | Allowed worktree root and Git common directory |
149
+ | `participants` | OS UID/GID/actor, native workspace and supervisor panes |
150
+ | `hitl` | Private store root, distinct socket, dedicated group, trusted grants path and optional installed TLS authority reference |
151
+
152
+ For example, the `hitl` entry inside an already selected project can be:
153
+
154
+ ```yaml
155
+ hitl:
156
+ root: /var/lib/lab/assign/atlas/hitl
157
+ socket_path: /run/lab/assign/atlas/hitl.sock
158
+ group: atlas-lifecycle
159
+ grants_path: /etc/lab/ace-lab/authorization.yml
160
+ ```
161
+
162
+ The store must be a separate child of that project's `service.state_root`; the
163
+ installer creates `service.state_root` with mode `0700`, the HITL root with its
164
+ maintained mode `0711`, and socket access before startup. The private parent keeps
165
+ the HITL tree inaccessible to other accounts; Store maintains private request and
166
+ secret subdirectories. Startup and restart validate the same layout.
167
+ `hitl.service_uid` in the grants document must equal the Assign owner UID. Native
168
+ identities come from the installed receipt and live observation, not the project
169
+ configuration cascade. HITL requests, decisions and merge claims use the same
170
+ canonical Assign journal and lifecycle exclusion.
171
+
172
+ For the existing remote Hermes transport, install its project-scoped mTLS
173
+ authority and add its absolute configuration path to this same `hitl` entry:
174
+
175
+ ```yaml
176
+ tls_authority: /etc/lab/ace-hitl/atlas-tls.json
177
+ ```
178
+
179
+ The referenced file uses the maintained `ace.hitl.tls.authority/v1` schema and
180
+ credential provisioning layout from `ace-hitl`. Its project must match this
181
+ owner; `service_user` and `service_group` must resolve to the owner's actual UID
182
+ and primary GID. The dedicated Unix control group remains `hitl.group`. Root
183
+ installs the credential references and Incus TCP proxy; callers cannot choose
184
+ them. Invalid ownership, identity, project or TLS material prevents startup.
185
+
186
+ The TLS listener runs in this same service process and shares its exact HITL
187
+ binding, store, decision policy and in-memory one-time OTP vault. Hermes uses its
188
+ verified project certificate as a remote transport identity; it does not need a
189
+ synthetic Unix transport UID. Keep `transport_uids` empty when no local transport
190
+ is installed. Configure the actual local proposer separately with
191
+ `proposal_uids` and its project grant. A proposer grant never authorizes replies
192
+ or delegates silence-based decisions. Restart the composed service after changing
193
+ its TLS authority or credential selection, retaining the canonical journal.
194
+
195
+ All selected endpoints must become ready before assignment ingress opens. A
196
+ failed endpoint shuts down the complete owner. A stale assignment endpoint needs
197
+ installer inspection; startup does not remove it or recover merge effects first.
198
+
199
+ ## Ordinary assignment client
200
+
201
+ `ace-lab assignment` selects the root-installed project owner and uses the
202
+ existing Assign protocol. Assign remains the only accepted execution journal.
203
+ Preparation captures reviewed task context and the existing managed assignment
204
+ graph; it does not start a worker or register accepted work.
205
+
206
+ ```sh
207
+ ace-lab assignment prepare --project atlas --task 8wr.t.abc --pane w1:pS \
208
+ --output .ace-local/prepared-task
209
+ ace-lab assignment request --project atlas --input .ace-local/prepared-task/request.json
210
+ ```
211
+
212
+ The output directory must not exist. Preparation retains the definition, bundle,
213
+ and registration request with one stable mutation ID. `--dependency-reports FILE`
214
+ selects an explicit JSON array of `{task_id, assignment_id, number}` entries for
215
+ dependency reports. A failed preparation keeps partial files for inspection;
216
+ it never rebuilds an existing directory. In this source checkout use
217
+ `bundle exec ace-lab` because no `bin/ace-lab` binstub is committed.
218
+
219
+ Every request file has exactly these fields:
220
+
221
+ ```json
222
+ {
223
+ "version": 1,
224
+ "project_id": "atlas",
225
+ "operation": "start_attempt",
226
+ "mutation_id": "original-start-001",
227
+ "params": {
228
+ "assignment_id": "assignment-id",
229
+ "attempt_id": "attempt-id",
230
+ "expected_generation": 0,
231
+ "scope": "010",
232
+ "head": "0123456789012345678901234567890123456789",
233
+ "pane": "w1:p2"
234
+ },
235
+ "parts": []
236
+ }
237
+ ```
238
+
239
+ Use actual retained IDs, scope, head and native pane. The actual worker executes
240
+ its start request: the owner verifies caller ancestry, live process and checkout.
241
+ A supervisor cannot impersonate another pane by changing `pane`. A new terminal
242
+ or prompt acknowledgement alone is not running proof.
243
+
244
+ | Operation | `params` beyond `assignment_id` | Transfer / authority |
245
+ |---|---|---|
246
+ | `register_assignment` | Generated by prepare | Exact prepared bundle; supervisor |
247
+ | `registration_status` | None | Read; `mutation_id: null` |
248
+ | `start_attempt` | `attempt_id`, `expected_generation: 0`, `scope`, `head`, `pane` | Actual worker start |
249
+ | `assignment_snapshot` | `attempt_id` (or null), `kind: queue` or `prepared_work` | Download; `mutation_id: null` |
250
+ | `queue_start` | `attempt_id`, `expected_generation`, `pane`, `step` (or null for next) | Original worker |
251
+ | `queue_finish` | Same as queue_start | One UTF-8 report part |
252
+ | `queue_fail` | `attempt_id`, `expected_generation`, `pane`, `message` | Original worker; unfinished work stays failed |
253
+ | `submit_result` | `attempt_id`, `expected_generation`, `pane`, `head`, `receipt_sha256` | Receipt first, then its ordered artifacts; original worker |
254
+ | `result_status` | `attempt_id` | Read; `mutation_id: null` |
255
+ | `result_snapshot` | `attempt_id`, `result_id` | Canonical receipt/artifacts download; `mutation_id: null` |
256
+ | `finish` | `attempt_id`, `expected_generation`, `pane`, `head`, `result_id`, `receipt_digest` | Supervisor accepts exact submitted result after settlement |
257
+
258
+ Upload `parts` are ordered `{file, bytes, sha256}` entries. Relative files resolve
259
+ beside the retained request. Changed, oversized or symlinked inputs refuse before
260
+ connection. Requesters never supply a wire `transfer` descriptor. Downloads require
261
+ `--output NEW_DIRECTORY`; numbered byte files and `response.json` are private
262
+ projections of the authenticated reply, not another execution store. For queue
263
+ snapshots, the first byte file contains the current step instructions and reports
264
+ as JSON; prepared-work snapshots contain the original Git bundle.
265
+
266
+ Retain each request and its exact parts before invoking it. On response loss,
267
+ inspect `registration_status` or `result_status` and reuse the same original
268
+ mutation when replay is appropriate. Never refresh its generation, replace its
269
+ bytes, or create a second worker because a reply was lost. Queue completion and
270
+ `submit_result` keep the attempt running; only the canonical accepted `finish`
271
+ reply proves terminal completion. Unknown effect or Inbox settlement refuses it.
272
+
273
+ These commands do not silently route to a local `ace-assign` cache. Missing or
274
+ changed installed owner selection is an explicit failure. Native wake consumers
275
+ must read the same canonical status and adopt its original attempt/result IDs.
276
+ Changed owner selection refuses new admission and dispatch until a controlled
277
+ restart. Existing canonical receipts remain the source for reconciliation.
278
+
279
+ ## Ordinary candidate, review and merge evidence
280
+
281
+ Keep the real delivery attempt running through review, test execution and merge.
282
+ Finish it only after settlement. A completed coder attempt can seed a new real
283
+ registered delivery attempt; never reopen its old authority.
284
+
285
+ The existing `assignment request --input request.json` interface also supports:
286
+
287
+ | Operation | Params in addition to assignment_id/attempt_id/head/candidate_generation | Authority / transfer |
288
+ |---|---|---|
289
+ | `submit_candidate` | `pane`, `expected_generation` | Original worker; one complete Git bundle; candidate_generation is the previous generation (0 initially) |
290
+ | `assign_review` | `pane`, `expected_generation`, `reviewer_assignment_id`, `reviewer_attempt_id` | Current canonical supervisor selects an already started independent reviewer |
291
+ | `export_candidate` | `pane`, `review_id` | Assigned reviewer; null mutation; `--output NEW_DIRECTORY` downloads canonical bundle |
292
+ | `accept_review` | `pane`, `expected_generation`, `review_id`, `receipt_sha256` | Assigned reviewer uploads exact receipt followed by its ordered report artifacts |
293
+ | `cancel_review` | `pane`, `expected_generation`, `review_id` | Current supervisor revokes that exact review |
294
+ | `review_status` | None | Null mutation; revalidates accepted original receipt and artifacts |
295
+ | `merge_evidence` | `test_execution_ids` (nonempty array) | Null mutation; projects exact accepted review and canonical executed-test references |
296
+
297
+ First submit the candidate, retaining its returned head, tree and new generation.
298
+ Prepare/register/start a separate review assignment through the ordinary native
299
+ entry. Its agent must have a different actual process generation, session UUID
300
+ and pane. The supervisor assigns that exact reviewer assignment/attempt at its
301
+ honestly observed starting base. Export gives the assigned agent canonical candidate
302
+ bytes; it then materializes/checks out the candidate. Receipt acceptance requires
303
+ that exact candidate HEAD and the unchanged original native reviewer binding. A receipt uses the delivery attempt's identifiers,
304
+ scope and actual producer; its `review.reviewer.actor` is the real reviewer's
305
+ account. The owner derives independence from authenticated native origins, so
306
+ shared-account agents do not need invented actor aliases.
307
+
308
+ `accept_review` retains the original receipt and up to 15 report artifacts in
309
+ CanonicalEvidence, verifies their exact digests, and returns the accepted review
310
+ ID/digest. The receipt declares operation review, succeeded, approved at the exact
311
+ candidate head, and executed review checks. These review checks are not test
312
+ execution authority. Missing/corrupt reports, a different caller, terminal delivery
313
+ attempt, changed head/generation or cancellation refuse. Retrying the exact
314
+ accepted mutation cannot accept a second review; changed bytes under that mutation
315
+ refuse. This first ingress accepts direct independent review of the exact candidate
316
+ generation. Campaign receipts and accepted-head carry are deliberately unavailable
317
+ until the existing ace-review campaign/assessment owner is connected; a head change
318
+ currently refuses prior approval. This is a remaining delivery capability, not the
319
+ final policy of requiring a full independent review after every SHA change.
320
+
321
+ Execute the project's root-selected test command through the existing command
322
+ transport. Only an explicit `purpose: test` definition with exact immutable
323
+ candidate head/tree, root-witnessed child, and canonical exit0/no signal can supply
324
+ test evidence. `merge_evidence` resolves those execution IDs and returns
325
+ `review_evidence` and `test_evidence` for the fixed merge request. Copy those exact
326
+ projections into the retained request before obtaining its digest-bound decision.
327
+ Plain worker check strings or generic allowed commands are not substitutes.
328
+
329
+ The sole owner revalidates evidence in the merge admission CAS and before each
330
+ Forge effect. Assignment exclusion prevents candidate changes, review cancellation
331
+ or finish racing an executing merge. Policy revocation or missing canonical bytes
332
+ refuses without dispatch. Existing unknown outcomes retain their original merge
333
+ reference and require reconciliation.
334
+
335
+ The dynamic candidate-to-immutable-command-view binding and installed two-worker
336
+ acceptance remain separate required gates. Static source fixtures do not authorize
337
+ per-PR administrative head/path edits or claim installed autonomous merging.
338
+
339
+ ## Ordinary driver and supervisor recovery
340
+
341
+ `ace-overseer ordinary --operation prepare` retains one context per fresh worker.
342
+ From that worker's native tool process, drive only the installed owner:
343
+
344
+ ```sh
345
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation status
346
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation start
347
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation bootstrap --output /private/work/accepted-context
348
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation next
349
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation step-start --generation 1 --step 010.01.01
350
+ ace-lab assignment drive --project atlas --context /private/work/context.json --operation step-finish --generation 2 --step 010.01.01 --report /private/work/check.md
351
+ ```
352
+
353
+ Use returned generations and actual step numbers, not the illustrative values.
354
+ `HERDR_PANE` supplies the caller pane unless `--pane` is explicit; the selected
355
+ owner authenticates actual native ancestry independently. `bootstrap` returns
356
+ accepted context paths, byte counts, SHA256 values and manifest digest. `next`
357
+ reads the canonical queue including reports and returns current/next instructions.
358
+ `fail --generation N --message REASON` records a failed scope; it never fabricates
359
+ successful completion. The workflow `wfi://assign/drive` selects this protocol
360
+ before local assignment resolution when `--ordinary`/the ordinary environment
361
+ is present.
362
+
363
+ Submit one bounded execution receipt JSON with the following fields, using
364
+ actual observed values (no placeholders in submitted evidence):
365
+
366
+ ```json
367
+ {
368
+ "assignment_id": "original-assignment", "attempt_id": "original-attempt",
369
+ "project_id": "atlas", "scope": "010.01", "operation": "check",
370
+ "producer": {"actor": "actual-user", "role": "worker", "runtime": "herdr"},
371
+ "head": "0123456789012345678901234567890123456789",
372
+ "verdict": "succeeded", "checks": [{"name": "tests", "verdict": "passed"}],
373
+ "artifacts": [{"path": "check.md", "sha256": "actual-64-hex-artifact-digest"}]
374
+ }
375
+ ```
376
+
377
+ The producer comes from canonical start status; checks and artifacts must describe
378
+ executed work. Run `--operation result --generation N --receipt receipt.json
379
+ --artifact check.md` with files in receipt order. The existing receipt verifier
380
+ and canonical import validate attribution, evidence and exact bytes. A result
381
+ submission leaves the worker running until its canonical supervisor executes
382
+ `--operation adopt` and receives terminal status. Keep the actual worker process
383
+ and checkout alive until that acceptance; a terminal pane without the required
384
+ native binding is insufficient.
385
+
386
+ Mutations retain their exact request and transferred bytes under the original
387
+ context directory before sending. A lost response is recovered with canonical
388
+ `status`/`next`, then `--operation replay --request /original/requests/.../request.json`.
389
+ Never update an existing request's generation, mutation ID or bytes to force a
390
+ retry. The requester directory is not an accepted-state store. The result hint
391
+ file is ignored by canonical reads/adoption, including when forged or stale.
392
+ Inbound Inbox settlement is not provisioned by this driver; nonempty references
393
+ still require the separately selected existing Inbox owner and fail closed otherwise.
394
+
395
+ A replacement coordinator reads `--operation supervisor`, then from its actual
396
+ installed native supervisor placement uses:
397
+
398
+ ```sh
399
+ ace-lab assignment drive --project atlas --context /private/work/context.json \
400
+ --operation adopt-supervisor --generation 1 --previous-binding-sha256 EXACT_OLD_DIGEST
401
+ ```
402
+
403
+ The definition chain records old→new bindings under its original generation CAS.
404
+ Only a positively ended old generation (kernel process absence or a different
405
+ exact process birth) permits adoption; live, inaccessible, remote-host and unknown
406
+ identities refuse. A lost adoption response reuses its original retained request.
407
+ No worker is re-registered or relaunched. Existing pending results, uncertain
408
+ operations, attempts and request IDs remain unchanged. Finish, including replay,
409
+ checks the current canonical supervisor binding and rejects the displaced one.
410
+
411
+ Wire additions are `supervisor_status` (assignment only, null mutation), and
412
+ `adopt_supervisor` (assignment, actual pane, expected definition generation,
413
+ previous_binding_sha256; original mutation). They use the same installed owner,
414
+ project policy, journal and lifecycle exclusion as ordinary execution.
415
+
124
416
  ## Error semantics
125
417
 
126
418
  Errors are one deterministic JSON document on stdout plus a non-zero exit.
@@ -292,3 +584,54 @@ Handlers retain the fixed closed environment and 30-second execution deadline, w
292
584
  The existing protected receiver exposes `recover_no_effect(binding:, input_bytes:, mutation_id:, expected_generation:)`. Supply the original assignment/attempt/candidate/head/request binding, original structured input, stable mutation ID and unchanged original positive authority generation. The receiver snapshots those inputs, authenticates canonical status and input before any settled return, and adopts an already accepted current challenge without claiming again. A needed claim uses the supplied generation unchanged. Lost acknowledgements remain uncertain; status retry never repeats the original effect.
293
585
 
294
586
  Trusted operation configuration must provide a distinct fixed `no_effect_argv` for the original project/service/executor. Inspection may proceed after the effect lease expires, but it never renews effect permission or selects normal effect argv. The selected inspector must produce operation-specific original target/handler/writer observations and existing strict challenge-bound artifacts; only the authority's canonical import can establish `failed-settled`. This source API and controlled composition do not deliver the outstanding gad.b inspector or installed acceptance.
587
+
588
+ ## Project commands
589
+
590
+ `ace-lab run --project ace -- /usr/bin/id` invokes one exact installed command
591
+ policy. `--as publisher` selects a permitted executor; repeated `--service NAME`
592
+ replaces the configured default set. Every service and the entire combination
593
+ must be granted. Arguments after `--` remain literal, including command options.
594
+ No shell is added. Duplicate `--project`/`--as` or empty argv are usage errors.
595
+
596
+ The root-owned `/etc/lab/ace-lab/project-commands.yml` is the only command-policy
597
+ source. Project preferences and caller environment cannot create grants. Its
598
+ version1 project entries contain `launcher`, `default_executor`, `default_services`,
599
+ `executors`, `services`, `commands`, and `grants`. Executors declare UID/GID/groups,
600
+ HOME/cwd/PATH and nonsecret environment; services declare nonsecret environment
601
+ and secret references; commands bind exact argv, executable SHA256, source
602
+ head/tree and required `purpose: command` or `purpose: test`. Test purpose requires
603
+ nonnull source head/tree and is never inferred from executable name or exit0.
604
+ With `purpose: command`, source head/tree may both be null for a non-Git probe;
605
+ Git source uses two exact object IDs. Grants bind requester kind/UID, command, executor
606
+ and the exact service combination. Accepted code/cwd must be root-owned and
607
+ unwritable by the executor. Missing or changed context refuses execution.
608
+
609
+ An operator grant authorizes an OS account. Agents sharing that account share
610
+ its operator authority; an environment hint does not prove human presence.
611
+ Agent-selected invocation additionally checks actual current native ancestry.
612
+ Separate identities are required when operator authority must exclude agents.
613
+
614
+ The CLI retains an invocation under `.ace-local/lab-commands/` and prints its
615
+ execution reference before contacting the canonical Assign owner. Inspect it with
616
+ `ace-lab run-status --project ace --reference REF`. Inspection starts no command
617
+ and remains available to the original requester account after a grant is revoked.
618
+ After response loss, inspect that original reference before requesting new work.
619
+ An existing claim or a replayed acknowledgment cannot release another child.
620
+
621
+ Interactive terminals preserve no-echo input, dimensions and signals. Pipe mode
622
+ preserves EOF, separate stdout/stderr and actual exit/signal status. Socket closure
623
+ without the authenticated result is uncertainty. Root launch/exit observations
624
+ remain separate from accepted product delivery evidence; exit0 alone does not
625
+ prove publication, installation or completed delivery.
626
+
627
+ This implementation slice admits nonsecret commands. A nonempty selected secret
628
+ reference set is explicitly refused before claim until the secret execution
629
+ capability is supplied. Publication also requires the publisher's writable
630
+ artifact/cache destination outside immutable accepted source, or retained exact
631
+ artifacts. These remaining joins and installed acceptance are tracked by ei9 .5;
632
+ source/kernel fixture success does not assert installed autonomous publication.
633
+
634
+ In interactive mode, input EOF sends the terminal's conventional Ctrl-D byte; a
635
+ program that changes its terminal to raw mode may interpret that byte itself.
636
+ Noninteractive EOF closes the command input pipe. The dedicated Linux helper owns
637
+ and reaps detached command descendants as well as the original process group.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../organisms/ordinary_assignment_client"
4
+ require "ace/assign/organisms/ordinary_driver"
5
+
6
+ module Ace
7
+ module Lab
8
+ module CLI
9
+ module Commands
10
+ module Assignment
11
+ class Drive < Ace::Support::Cli::Command
12
+ desc "Drive or inspect one original ordinary execution through its installed owner"
13
+ option :project, required: true, desc: "Installed project ID; never inferred from local Assign"
14
+ option :context, required: true, desc: "Original retained ordinary context.json"
15
+ option :operation, required: true, desc: "status, supervisor, adopt-supervisor, bootstrap, next, start, step-start, step-finish, fail, result, adopt, replay"
16
+ option :previous_binding_sha256, desc: "Exact canonical old supervisor binding digest for adoption"
17
+ option :output, desc: "New private directory for accepted bootstrap context"
18
+ option :pane, desc: "Actual native caller pane (defaults to HERDR_PANE)"
19
+ option :generation, type: :integer, desc: "Exact canonical generation observed before mutation"
20
+ option :step, desc: "Exact queue step within the retained scope"
21
+ option :report, desc: "Executed step's bounded UTF-8 report file"
22
+ option :message, desc: "Failure reason"
23
+ option :receipt, desc: "Attributable execution receipt JSON"
24
+ option :artifact, type: :array, desc: "Artifact files in receipt order"
25
+ option :request, desc: "Original mutation request to replay after reconciliation"
26
+
27
+ def call(project:, context:, operation:, **options)
28
+ transport = Organisms::OrdinaryAssignmentClient.new(project: project)
29
+ driver = Ace::Assign::Organisms::OrdinaryDriver.new(path: context, project: project, transport: transport)
30
+ pane = options[:pane] || ENV["HERDR_PANE"]
31
+ result = case operation
32
+ when "status" then driver.status
33
+ when "supervisor" then driver.supervisor
34
+ when "adopt-supervisor"
35
+ driver.adopt_supervisor(pane: pane, generation: options.fetch(:generation),
36
+ previous_binding_sha256: options.fetch(:previous_binding_sha256))
37
+ when "bootstrap" then driver.bootstrap(output: options.fetch(:output))
38
+ when "next" then driver.queue
39
+ when "start" then driver.start(pane: pane)
40
+ when "step-start", "step-finish", "fail"
41
+ driver.progress(operation: {"step-start" => "queue_start", "step-finish" => "queue_finish", "fail" => "queue_fail"}.fetch(operation),
42
+ pane: pane, generation: options.fetch(:generation), step: options[:step],
43
+ report: operation == "step-finish" ? options.fetch(:report) : nil,
44
+ message: operation == "fail" ? options.fetch(:message) : nil)
45
+ when "result"
46
+ driver.submit(pane: pane, generation: options.fetch(:generation), receipt: options.fetch(:receipt), artifacts: options[:artifact] || [])
47
+ when "adopt" then driver.adopt(pane: pane)
48
+ when "replay" then driver.replay(input: options.fetch(:request))
49
+ else raise ArgumentError, "unknown ordinary driver operation"
50
+ end
51
+ puts JSON.generate(result)
52
+ rescue ArgumentError, KeyError, SystemCallError, Ace::Assign::Error, Ace::Runtime::Error, Ace::Lab::Error => error
53
+ raise Ace::Support::Cli::Error, "ordinary execution unavailable: #{error.message}; inspect the original context with --operation status"
54
+ end
55
+ end
56
+
57
+ class Prepare < Ace::Support::Cli::Command
58
+ desc "Retain reviewed task input for ordinary Assign registration"
59
+ option :project, required: true, desc: "Installed project ID"
60
+ option :task, required: true, desc: "Reviewed leaf task ID"
61
+ option :pane, required: true, desc: "Actual supervisor pane for registration"
62
+ option :output, required: true, desc: "New private directory for retained input"
63
+ option :dependency_reports, desc: "JSON array of explicit dependency report selections"
64
+
65
+ def call(project:, task:, pane:, output:, **options)
66
+ reports = options[:dependency_reports] ? JSON.parse(Ace::Assign::Authority::RetainedInput.read_file(
67
+ options.fetch(:dependency_reports), 65_536), allow_duplicate_key: false, create_additions: false, max_nesting: 16) : []
68
+ puts JSON.generate(Organisms::OrdinaryAssignmentClient.prepare(project: project, task: task,
69
+ pane: pane, output: output, dependency_reports: reports))
70
+ rescue JSON::ParserError, ArgumentError, KeyError, SystemCallError, Ace::Assign::Error, Ace::Runtime::Error => error
71
+ raise Ace::Support::Cli::Error, "ordinary preparation incomplete: #{error.message}; retain existing files"
72
+ end
73
+ end
74
+
75
+ class Request < Ace::Support::Cli::Command
76
+ desc "Send one retained operation to the installed ordinary Assign owner"
77
+ option :project, required: true, desc: "Installed project ID"
78
+ option :input, required: true, desc: "Retained request JSON with original mutation and part digests"
79
+ option :output, desc: "New private directory for a canonical snapshot download"
80
+
81
+ def call(project:, input:, **options)
82
+ service = Organisms::OrdinaryAssignmentClient.new(project: project)
83
+ puts JSON.generate(service.request(input: input, output: options[:output]))
84
+ rescue ArgumentError, KeyError, SystemCallError, Ace::Assign::Error, Ace::Runtime::Error, Ace::Lab::Error => error
85
+ raise Ace::Support::Cli::Error, "ordinary assignment unavailable: #{error.message}; retain the original request; inspect status before new work"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ require_relative "../../organisms/command_executor"
3
+ module Ace
4
+ module Lab
5
+ module CLI
6
+ module Commands
7
+ class CommandExecute < Ace::Support::Cli::Command
8
+ desc "Execute one admitted command for the installed root launcher"
9
+ option :project, required: true
10
+ option :fd, type: :integer, required: true
11
+ def call(project:, fd:, **)
12
+ raise SecurityError unless Process.uid.zero? && Process.euid.zero? && fd >= 3
13
+ bytes = $stdin.read(16_385)
14
+ raise ArgumentError if bytes.bytesize > 16_384
15
+ request = JSON.parse(bytes, allow_duplicate_key: false, create_additions: false, max_nesting: 16)
16
+ socket = UNIXSocket.for_fd(fd)
17
+ Organisms::CommandExecutor.new(project: project).run(socket: socket, request: request)
18
+ 0 # Child status belongs to the framed result, not helper transport.
19
+ rescue StandardError, SecurityError
20
+ raise Ace::Support::Cli::Error, "command uncertain; inspect original execution reference"
21
+ ensure
22
+ socket&.close unless socket&.closed?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ require "optparse"
3
+ require_relative "../../organisms/command_run_client"
4
+ module Ace
5
+ module Lab
6
+ module CLI
7
+ module Commands
8
+ class Run < Ace::Support::Cli::Command
9
+ desc "Run one explicitly authorized project command"
10
+ option :project, required: true, desc: "Installed project ID"
11
+ option :as, desc: "Configured permitted executor"
12
+ option :service, repeat: true, desc: "Replace default services with this complete set"
13
+ argument :argv, type: :array, desc: "Literal command argv after --"
14
+
15
+ def self.parse(args)
16
+ separator = args.index("--")
17
+ raise ArgumentError, "run requires -- followed by literal command argv" unless separator && separator < args.size - 1
18
+ switches, argv = args.first(separator), args.drop(separator + 1)
19
+ options = {executor: nil, services: nil}
20
+ seen = {}
21
+ parser = OptionParser.new
22
+ %w[project as].each do |name|
23
+ parser.on("--#{name} VALUE") do |value|
24
+ raise ArgumentError, "duplicate --#{name}" if seen[name]
25
+ seen[name] = true
26
+ options[name == "as" ? :executor : :project] = value
27
+ end
28
+ end
29
+ parser.on("--service VALUE") { |value| (options[:services] ||= []) << value }
30
+ parser.parse!(switches)
31
+ raise ArgumentError, "run requires one --project and no extra options" unless seen["project"] && switches.empty?
32
+ options.merge(argv: argv)
33
+ rescue OptionParser::ParseError, ArgumentError
34
+ raise Ace::Support::Cli::Error, "usage_error: run requires unique project/executor options and literal argv after --"
35
+ end
36
+
37
+ def self.invoke(args)
38
+ options = parse(args)
39
+ project = options.delete(:project)
40
+ client = Organisms::CommandRunClient.new(project: project)
41
+ begin
42
+ client.run(**options, pane: ENV["HERDR_PANE_ID"])
43
+ rescue StandardError, SecurityError
44
+ # Once admission may have crossed the owner boundary, even a
45
+ # malformed stream is uncertainty, never a fresh usage refusal.
46
+ raise Ace::Support::Cli::Error, "command_uncertain: inspect the original execution reference before new work"
47
+ end
48
+ rescue Ace::Lab::InvalidConfigurationError
49
+ raise Ace::Support::Cli::Error, "invalid_command_context: installed project selection is unavailable"
50
+ end
51
+ end
52
+ class RunStatus < Ace::Support::Cli::Command
53
+ desc "Inspect an original command execution without running it"
54
+ option :project, required: true
55
+ option :reference, required: true
56
+ def call(project:, reference:, **)
57
+ puts JSON.generate(Organisms::CommandRunClient.new(project: project).status(reference))
58
+ rescue StandardError, SecurityError
59
+ raise Ace::Support::Cli::Error, "command_status_unavailable: preserve original execution reference"
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end