ace-herdr 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/.ace-defaults/herdr/config.yml +0 -4
- data/CHANGELOG.md +18 -0
- data/docs/usage.md +57 -35
- data/lib/ace/herdr/cli/commands/inbox.rb +83 -21
- data/lib/ace/herdr/cli/commands/native_source.rb +56 -0
- data/lib/ace/herdr/cli.rb +3 -0
- data/lib/ace/herdr/errors.rb +23 -0
- data/lib/ace/herdr/molecules/bounded_process.rb +261 -52
- data/lib/ace/herdr/molecules/delivery_record_store.rb +81 -6
- data/lib/ace/herdr/molecules/guarded_native_origin.rb +59 -0
- data/lib/ace/herdr/molecules/herdr_executor.rb +6 -3
- data/lib/ace/herdr/molecules/inbox_cli_input.rb +36 -0
- data/lib/ace/herdr/molecules/inbox_context_client.rb +88 -0
- data/lib/ace/herdr/molecules/inbox_context_completion_client.rb +119 -0
- data/lib/ace/herdr/molecules/inbox_context_key.rb +63 -0
- data/lib/ace/herdr/molecules/inbox_context_lifetime.rb +122 -0
- data/lib/ace/herdr/molecules/inbox_context_native_process.rb +92 -0
- data/lib/ace/herdr/molecules/inbox_context_service_configuration.rb +183 -0
- data/lib/ace/herdr/molecules/inbox_context_store.rb +159 -0
- data/lib/ace/herdr/molecules/inbox_context_wire.rb +76 -0
- data/lib/ace/herdr/molecules/inbox_direct_effect_binding.rb +92 -0
- data/lib/ace/herdr/molecules/inbox_direct_result.rb +65 -0
- data/lib/ace/herdr/molecules/native_queue_executor.rb +10 -21
- data/lib/ace/herdr/molecules/native_source.rb +189 -0
- data/lib/ace/herdr/molecules/protected_inbox_selection.rb +126 -0
- data/lib/ace/herdr/molecules/protected_native_control.rb +129 -4
- data/lib/ace/herdr/native_source/HERDR-LICENSE +201 -0
- data/lib/ace/herdr/native_source/guarded-prompt.patch +3493 -0
- data/lib/ace/herdr/native_source/selection.json +24 -0
- data/lib/ace/herdr/organisms/inbox.rb +293 -203
- data/lib/ace/herdr/organisms/inbox_context_direct_effects.rb +285 -0
- data/lib/ace/herdr/organisms/inbox_context_effects.rb +30 -0
- data/lib/ace/herdr/organisms/inbox_context_listener.rb +151 -0
- data/lib/ace/herdr/organisms/inbox_context_owner.rb +550 -0
- data/lib/ace/herdr/organisms/inbox_context_server.rb +70 -0
- data/lib/ace/herdr/organisms/inbox_context_service.rb +107 -0
- data/lib/ace/herdr/organisms/inbox_guarded_wake.rb +117 -0
- data/lib/ace/herdr/organisms/native_source_builder.rb +183 -0
- data/lib/ace/herdr/version.rb +1 -1
- data/lib/ace/herdr.rb +5 -28
- metadata +68 -9
- data/lib/ace/herdr/organisms/protected_inbox.rb +0 -73
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00422d9f6820e48b0ea45698e4663f7ecaeb9b5b01ddf1626ab86aed50adcbfd
|
|
4
|
+
data.tar.gz: 2632e4056271d3c022056a70fa5f3b95e2f69ae08544bf70f2577bc11fbac4f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c6626094540195dadf06116add8bd27708934b809347c12cb0bc355cb6c03d5ff32e30e3e63870e158316cf9c80de7e2bdd5cad5f731cea4fef11303c1c9bd8a
|
|
7
|
+
data.tar.gz: 7b4e02b683be089a602d10c7fafcb09abc134a9f5b60e5a58a8d0ef42b35bb0ec18555700e0c316ec5de12aae7e5c01a4f0ffede12e26597f1a881fcbb5d50af
|
|
@@ -19,10 +19,6 @@ timeouts:
|
|
|
19
19
|
# Delivery record root (ADR-029 short-name layout), relative to project root
|
|
20
20
|
deliveries_dir: .ace-local/herdr/deliveries
|
|
21
21
|
|
|
22
|
-
# Absolute path to the supervisor-managed receipt verification key. Configure
|
|
23
|
-
# before enqueue; each event pins this key's fingerprint for reconciliation.
|
|
24
|
-
inbox_receipt_public_key:
|
|
25
|
-
|
|
26
22
|
# Tidy (spec 8wq.t.1w0): archive delivered records strictly older than this
|
|
27
23
|
# many days (age = record updated_at); failed/retryable/pending are never
|
|
28
24
|
# touched
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.1] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Require the published producer and consumer patch versions containing the protected context and managed-attempt APIs; exclude incompatible prior dependency graphs.
|
|
15
|
+
|
|
16
|
+
## [0.5.0] - 2026-10-08
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Submit Codex answers through the existing Herdr prompt command to the original live terminal. A successful send means submitted, not read; retain unknown sends without automatic resend.
|
|
21
|
+
- Remove dedicated Codex app-server selection, message-read observation, signing keys and consumed/superseded reconciliation APIs.
|
|
22
|
+
- Keep authenticated original-context admission and actual scoped effects without a second current-state projection.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Load bounded process/error primitives directly and preserve bounded cleanup of the original process after execution uncertainty.
|
|
27
|
+
|
|
10
28
|
## [0.4.0] - 2026-10-05
|
|
11
29
|
|
|
12
30
|
### Fixed
|
data/docs/usage.md
CHANGED
|
@@ -14,7 +14,7 @@ ace-docs:
|
|
|
14
14
|
## Command Surface
|
|
15
15
|
|
|
16
16
|
- `ace-herdr deliver [OPTIONS]`
|
|
17
|
-
- `ace-herdr inbox enqueue|status|deliver
|
|
17
|
+
- `ace-herdr inbox enqueue|status|deliver [OPTIONS]`
|
|
18
18
|
- `ace-herdr dispatch [OPTIONS]`
|
|
19
19
|
- `ace-herdr list [--panes|--tabs|--workspaces] [--workspace ID] [--quiet]`
|
|
20
20
|
- `ace-herdr send [--cmd TEXT] [--msg TEXT...] [--key NAME...] --pane ID [--quiet]`
|
|
@@ -53,7 +53,7 @@ remain valid; the codec also preserves paired native tmux IDs (`$0`/`%0`) when
|
|
|
53
53
|
carried by managed metadata. Mixed native/token or cross-field IDs refuse.
|
|
54
54
|
Wire and persisted references must be canonical. Syntax acceptance does not add
|
|
55
55
|
tmux execution to this Herdr controller or replace its exact native ownership,
|
|
56
|
-
target and
|
|
56
|
+
target and submission checks.
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
ace-herdr inbox enqueue --event inb-12345678 --attempt ATTEMPT --ref ref.json --file prompt.txt
|
|
@@ -61,39 +61,22 @@ ace-herdr inbox status --event inb-12345678 --format json
|
|
|
61
61
|
ace-herdr inbox deliver --event inb-12345678
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
`enqueue` checks the live pane,
|
|
64
|
+
`enqueue` checks the live pane, terminal and original agent-session identity.
|
|
65
|
+
Managed callers supply `expected_target` from the accepted runtime owner. Reusing
|
|
66
|
+
an event with identical attempt, target and payload returns its original record;
|
|
67
|
+
changed input refuses. `deliver` records one submission intent before sending.
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
Codex delivery uses the existing Herdr terminal prompt; Pi uses its maintained
|
|
70
|
+
queue boundary. `delivered` means the submission was accepted, not that the
|
|
71
|
+
agent read it or completed the task. A known pre-submission refusal stays queued.
|
|
72
|
+
A lost response, process interruption after intent, or changed target is uncertain;
|
|
73
|
+
a later `deliver` does not automatically resend or select a replacement terminal.
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
```json
|
|
75
|
-
{
|
|
76
|
-
"event_id": "inb-12345678",
|
|
77
|
-
"attempt_id": "ATTEMPT",
|
|
78
|
-
"claim_generation": 1,
|
|
79
|
-
"payload_sha256": "<digest from inbox status>",
|
|
80
|
-
"binding": {"session": "<exact binding from inbox status>"},
|
|
81
|
-
"outcome": "consumed",
|
|
82
|
-
"observer": {"role": "operator", "id": "operator-id"},
|
|
83
|
-
"evidence": {"kind": "consumed_acknowledged", "native_reference": "session-log:42", "observation": "message consumed and acknowledged"}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Copy the **entire** `binding` object from `inbox status`; the shortened object above only illustrates the field. `consumed` requires `evidence.kind: consumed_acknowledged`. `superseded` requires `queue_evicted`, `queue_expired`, or `thread_replaced`, with an observation that the old queue entry cannot be consumed. A matching receipt changes `uncertain` to `completed` for consumption, or to `queued` for proven supersession. The original target binding stays pinned. To authorize a replacement native session, include `replacement_target` in the signed receipt with the complete target identity from a fresh Herdr pane observation; reconciliation verifies its session, pane, terminal, agent, and thread against that live replacement address before saving it. Without that field, a new claim can only use the original target. A missing or mismatched receipt returns JSON with unchanged `state: uncertain` and `reconciliation_refusal`; it does not resend. Keep the observation record with the receipt. Signature validation authenticates the configured key; the operator or supervisor remains responsible for checking the cited native outcome before signing.
|
|
88
|
-
|
|
89
|
-
After inspecting the native outcome and writing `proof.json`, the trusted operator signs the exact bytes:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
openssl dgst -sha256 -sign operator-private.pem -out proof.json.sig proof.json
|
|
93
|
-
ace-herdr inbox reconcile --event inb-12345678 --receipt proof.json
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
The command verifies the detached signature against the configured public key before any transition. Missing, malformed, or self-signed receipts leave the event uncertain and return a machine-readable refusal. Keep the signed receipt and source observation for audit.
|
|
75
|
+
Inspect current progress directly through `ace-herdr capture` or the native pane.
|
|
76
|
+
Inbox status is transport history, not a cached projection of current agent state.
|
|
77
|
+
No agent-read proof, signing key, reconciliation command or dedicated Codex
|
|
78
|
+
app-server is required. Actual task results and scoped service effects retain
|
|
79
|
+
their independent authorization and verification.
|
|
97
80
|
|
|
98
81
|
## tmux-intent ↔ herdr-command parity
|
|
99
82
|
|
|
@@ -372,6 +355,45 @@ Commands raise a CLI error (non-zero exit) carrying herdr's machine code where o
|
|
|
372
355
|
|
|
373
356
|
Exit `0` means success (including an explicit empty `list` result or a satisfied wait).
|
|
374
357
|
|
|
375
|
-
|
|
358
|
+
Runtime recovery exposes the read-only `process_binding(pane:, caller_pid:)`
|
|
359
|
+
adapter operation, which checks OS ancestry, foreground ownership and the
|
|
360
|
+
original agent-session identity. Message submission is never task completion.
|
|
361
|
+
|
|
362
|
+
## Packaged guarded native source
|
|
363
|
+
|
|
364
|
+
`ace-herdr native-source selection` describes the source selection and patch shipped by this gem. These assets live under `lib/ace/herdr/native_source`; installed consumers do not need task directories. This source retains native version 0.9.3 and protocol 22; its exact source commit and patch digest identify the guarded producer. No upstream release or publication is implied.
|
|
365
|
+
|
|
366
|
+
An explicit nonroot build owner can run:
|
|
367
|
+
|
|
368
|
+
```sh
|
|
369
|
+
ace-herdr native-source build --source /absolute/clean/local/herdr-checkout --output /absolute/new/artifact-directory --target x86_64-unknown-linux-musl
|
|
370
|
+
ace-herdr native-source verify --artifact /absolute/artifact-directory
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
The build requires a matching native Linux host (x86_64 or aarch64), the selected Rust and Zig versions, and a native `musl-gcc` toolchain already provisioned by the build owner. Cross builds are unsupported. The output must be new. The builder reproduces the exact baseline plus shipped patch in an isolated checkout, checks the commit/tree/lock/toolchain bytes, pins resolved Rust/Zig/linker paths, invokes the locked release build, and records observed executable/tool digests. It never runs the resulting Herdr binary, changes global tool defaults, contacts a running Herdr server, or publishes an artifact. Locked dependency fetching by Cargo may require network access. Failed builds retain their output claim; a partial directory does not verify.
|
|
374
|
+
|
|
375
|
+
Verification checks bounded strict provenance against this gem's source selection and the observed ELF bytes/architecture. It provides byte provenance, not deployment authorization. The trusted Lab deployment owner separately selects the gem, source selection, artifact and receipt digests before installing the fixed Lab binary. Protected runtime replacement must also use the canonical maintenance admission/release owner. That operational installer join remains required source work; these commands alone do not activate the producer or complete installed acceptance.
|
|
376
|
+
|
|
377
|
+
The selected source also provides `terminal.inhibit_input` for an exact original terminal/origin. Its successful `terminal_input_drained` reply proves permanent input inhibition and zero queued input, including terminal replies; it does not prove child exit or canonical scope cleanup. Lost acknowledgement may be retried only against the same original actor. Source fixtures validate this contract without running a native binary; actual Lab installation and N2 consumption remain required.
|
|
378
|
+
|
|
379
|
+
The inbox context source owner supplies durable operation admission and exclusive key rotation through a fixed authenticated exchange. A lost begin reply recovers the same original process's grant; unknown effects, owner restart and incomplete installation retain blocking state. Replacement and rollback each require an attestation from the actual configured signer plus protected public/config readback. This core does not yet activate a context CLI/server, migrate Assign/private-store consumers, or authorize orphan reclamation. Existing protected mutations must all be migrated before this becomes an accepted end-to-end rotation path; installation and private-key pair handling remain the trusted provisioner's responsibility.
|
|
380
|
+
|
|
381
|
+
### Trusted bounded descriptor handoff
|
|
382
|
+
|
|
383
|
+
Internal source installers using `BoundedProcess.call` may supply
|
|
384
|
+
`descriptor_mapping: {4 => authenticated_readonly_file}` together with bounded
|
|
385
|
+
`stdin_data` carrying exact selected source bytes. The mapping has at most16
|
|
386
|
+
entries; child descriptor keys are strict Integers3..63 and handles must be live
|
|
387
|
+
read-only regular files. Stdio overwrite, writable/closed/nonregular handles and
|
|
388
|
+
ambient descriptor/path fallback refuse before spawn. Standard Open3 spawn maps
|
|
389
|
+
only explicit descriptors; `close_others: true` excludes all other descriptors.
|
|
390
|
+
The supplying verification owner keeps the handle scoped and authenticates the
|
|
391
|
+
selected inode; the runner does not confer authority or authenticate arbitrary
|
|
392
|
+
files. Actual installed privilege-transition/PAM preservation is a separate
|
|
393
|
+
installed acceptance check, not established by controlled file/child tests.
|
|
394
|
+
|
|
395
|
+
### Bounded subprocess cleanup
|
|
396
|
+
|
|
397
|
+
`BoundedProcess.call` retains capped stdout/stderr and uses an execution deadline plus at most one additional second of cleanup. `stderr_limit:` defaults to `output_limit:`. Unconfirmed cleanup raises `PostLaunchError` and transfers only the still-owned original child to an eventual reaper; no success or absence proof follows. Generic successful calls do not newly signal process groups.
|
|
376
398
|
|
|
377
|
-
|
|
399
|
+
The fixed protected handler selects `cleanup_group: true`: stream EOF alone never triggers termination. Nonreaping observation must establish the original leader exited before its group is signalled and the leader reaped. Loss of original child ownership refuses further signalling. Controlled source checks cover ownership/IO boundaries; installed native/descendant effectiveness belongs to gad.2.
|
|
@@ -4,6 +4,10 @@ require "json"
|
|
|
4
4
|
require "openssl"
|
|
5
5
|
require "ace/support/cli"
|
|
6
6
|
require_relative "support"
|
|
7
|
+
require_relative "../../molecules/protected_inbox_selection"
|
|
8
|
+
require_relative "../../molecules/inbox_context_client"
|
|
9
|
+
require_relative "../../molecules/inbox_cli_input"
|
|
10
|
+
require_relative "../../molecules/inbox_direct_effect_binding"
|
|
7
11
|
|
|
8
12
|
module Ace
|
|
9
13
|
module Herdr
|
|
@@ -13,25 +17,40 @@ module Ace
|
|
|
13
17
|
include Ace::Support::Cli::Base
|
|
14
18
|
include Runtime
|
|
15
19
|
|
|
16
|
-
desc "Enqueue, inspect,
|
|
17
|
-
argument :operation, desc: "enqueue, status,
|
|
20
|
+
desc "Enqueue, inspect, or deliver a durable agent inbox event"
|
|
21
|
+
argument :operation, desc: "enqueue, status, or deliver"
|
|
22
|
+
option :project, type: :string, desc: "Installed project ID"
|
|
23
|
+
option :mapping, type: :string, desc: "Installed mapping ID"
|
|
24
|
+
option :inbox_context, type: :string, desc: "Installed inbox context ID"
|
|
25
|
+
option :claim_generation, type: :integer, desc: "Original expected claim generation"
|
|
26
|
+
option :assignment, type: :string, desc: "Original assignment ID"
|
|
18
27
|
option :event, type: :string, desc: "Stable event ID"
|
|
19
28
|
option :attempt, type: :string, desc: "Assignment attempt ID"
|
|
20
29
|
option :ref, type: :string, desc: "Herdr reverse-address JSON file"
|
|
21
30
|
option :file, type: :string, desc: "Payload file"
|
|
22
|
-
option :receipt, type: :string, desc: "Native proof JSON file"
|
|
23
31
|
option :format, type: :string, desc: "Output format (json)"
|
|
24
32
|
|
|
25
|
-
def initialize(executor: nil, native: nil
|
|
33
|
+
def initialize(executor: nil, native: nil, selection: Molecules::ProtectedInboxSelection.new,
|
|
34
|
+
context_client_factory: Molecules::InboxContextClient.method(:selected),
|
|
35
|
+
kernel: Ace::Runtime::Molecules::ProtectedLinux.new)
|
|
26
36
|
@executor = executor
|
|
27
37
|
@native = native
|
|
38
|
+
@selection, @context_client_factory, @kernel = selection, context_client_factory, kernel
|
|
28
39
|
end
|
|
29
40
|
|
|
30
41
|
def call(operation: nil, **options)
|
|
31
42
|
inbox = nil
|
|
32
43
|
translate_errors do
|
|
44
|
+
cli_error("unsupported inbox operation") unless %w[enqueue status deliver].include?(operation)
|
|
33
45
|
cli_error("--event is required") if options[:event].to_s.empty?
|
|
34
46
|
cli_error("only --format json is supported") if options[:format] && options[:format] != "json"
|
|
47
|
+
protected = false
|
|
48
|
+
@selection.with(options) do |selected|
|
|
49
|
+
next unless selected
|
|
50
|
+
puts JSON.generate(protected_call(operation, options, selected))
|
|
51
|
+
protected = true
|
|
52
|
+
end
|
|
53
|
+
next if protected
|
|
35
54
|
inbox = Organisms::Inbox.from_config(config: config, executor: executor,
|
|
36
55
|
native: @native || Molecules::NativeQueueExecutor.new,
|
|
37
56
|
root: Dir.pwd)
|
|
@@ -44,24 +63,8 @@ module Ace
|
|
|
44
63
|
inbox.status(event: options[:event])
|
|
45
64
|
when "deliver"
|
|
46
65
|
inbox.deliver(event: options[:event])
|
|
47
|
-
when "reconcile"
|
|
48
|
-
path = options[:receipt]
|
|
49
|
-
begin
|
|
50
|
-
bytes = path.to_s.empty? ? nil : File.binread(path)
|
|
51
|
-
receipt = bytes && JSON.parse(bytes)
|
|
52
|
-
signature = bytes && File.binread("#{path}.sig")
|
|
53
|
-
rescue SystemCallError, JSON::ParserError => e
|
|
54
|
-
# Receipt input is caller-owned: unreadable or malformed
|
|
55
|
-
# proof files surface as the documented refusal, never as
|
|
56
|
-
# command failures.
|
|
57
|
-
puts JSON.generate(inbox.status(event: options[:event]).merge(
|
|
58
|
-
"reconciliation_refusal" => "invalid receipt: #{e.message}"))
|
|
59
|
-
return
|
|
60
|
-
end
|
|
61
|
-
inbox.reconcile(event: options[:event], receipt: receipt,
|
|
62
|
-
signed_bytes: bytes, signature: signature)
|
|
63
66
|
else
|
|
64
|
-
cli_error("operation must be enqueue, status,
|
|
67
|
+
cli_error("operation must be enqueue, status, or deliver")
|
|
65
68
|
end
|
|
66
69
|
puts JSON.generate(result)
|
|
67
70
|
end
|
|
@@ -69,6 +72,65 @@ module Ace
|
|
|
69
72
|
raise Ace::Support::Cli::Error, e.message
|
|
70
73
|
end
|
|
71
74
|
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def protected_call(operation, options, selected)
|
|
79
|
+
%i[event attempt assignment].each { |key| cli_error("--#{key} is required") if options[key].to_s.empty? }
|
|
80
|
+
unless %w[enqueue status deliver].include?(operation)
|
|
81
|
+
cli_error("protected operation requires its maintained direct handler")
|
|
82
|
+
end
|
|
83
|
+
allowed = %i[project mapping inbox_context event attempt assignment format]
|
|
84
|
+
allowed += %i[ref file] if operation == "enqueue"
|
|
85
|
+
allowed += %i[claim_generation] if %w[deliver].include?(operation)
|
|
86
|
+
if options.any? { |key, value| !value.nil? && !allowed.include?(key) }
|
|
87
|
+
cli_error("protected inbox operation options differ")
|
|
88
|
+
end
|
|
89
|
+
context = selected.fetch("context")
|
|
90
|
+
client = @context_client_factory.call(context_id: selected.fetch("inbox_context_id"),
|
|
91
|
+
socket_path: context.fetch("control_socket_path"), owner_credentials: context.fetch("owner_credentials"))
|
|
92
|
+
if operation == "status"
|
|
93
|
+
return client.request("status_context", {"event_id" => options.fetch(:event), "attempt_id" => options.fetch(:attempt), "original" => direct_original(selected, options)}).fetch("record")
|
|
94
|
+
end
|
|
95
|
+
if operation == "deliver"
|
|
96
|
+
unless options[:claim_generation].is_a?(Integer) && options[:claim_generation] >= 0
|
|
97
|
+
cli_error("--claim-generation must be an explicit nonnegative integer")
|
|
98
|
+
end
|
|
99
|
+
admission = direct_admission(client, selected, options, "deliver")
|
|
100
|
+
result = client.request("deliver_context", {"operation_id" => admission.fetch("operation_id"),
|
|
101
|
+
"key_generation" => admission.fetch("key_generation"), "event_id" => options.fetch(:event),
|
|
102
|
+
"attempt_id" => options.fetch(:attempt), "original" => direct_original(selected, options), "expected_claim_generation" => options.fetch(:claim_generation)})
|
|
103
|
+
unless %w[idle unknown].include?(result["admission_state"])
|
|
104
|
+
cli_error("direct delivery admission observation differs")
|
|
105
|
+
end
|
|
106
|
+
client.request("end_context_operation", {"operation_id" => admission.fetch("operation_id")}) if result.fetch("admission_state") == "idle"
|
|
107
|
+
return result.fetch("record").merge("admission_state" => result.fetch("admission_state"))
|
|
108
|
+
end
|
|
109
|
+
%i[ref file].each { |key| cli_error("--#{key} is required") if options[key].to_s.empty? }
|
|
110
|
+
reverse = Molecules::InboxCliInput.reverse(options.fetch(:ref))
|
|
111
|
+
payload = Molecules::InboxCliInput.read(options.fetch(:file), limit: 65_536).dup.force_encoding(Encoding::UTF_8)
|
|
112
|
+
cli_error("payload encoding differs") unless payload.valid_encoding? && !payload.include?("\0")
|
|
113
|
+
admission = direct_admission(client, selected, options, "enqueue")
|
|
114
|
+
result = client.request("enqueue_context", {"operation_id" => admission.fetch("operation_id"),
|
|
115
|
+
"key_generation" => admission.fetch("key_generation"), "event_id" => options.fetch(:event),
|
|
116
|
+
"attempt_id" => options.fetch(:attempt), "original" => direct_original(selected, options), "reverse" => reverse, "payload_bytes" => payload.bytesize,
|
|
117
|
+
"payload_sha256" => Digest::SHA256.hexdigest(payload)}, payload: payload)
|
|
118
|
+
client.request("end_context_operation", {"operation_id" => admission.fetch("operation_id")})
|
|
119
|
+
result.fetch("record").merge("admission_state" => result.fetch("admission_state"))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def direct_original(selected, options)
|
|
123
|
+
value = selected.slice("project_id", "mapping_id", "inbox_context_id").merge("assignment_id" => options.fetch(:assignment))
|
|
124
|
+
Molecules::InboxDirectEffectBinding.original!(value)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def direct_admission(client, selected, options, purpose)
|
|
128
|
+
peer = @kernel.capture(Process.pid)
|
|
129
|
+
client.request("begin_context_operation", {"context_id" => selected.fetch("inbox_context_id"),
|
|
130
|
+
"purpose" => purpose, "event_id" => options.fetch(:event), "process_binding" => peer,
|
|
131
|
+
"original" => direct_original(selected, options).merge("attempt_id" => options.fetch(:attempt))})
|
|
132
|
+
end
|
|
133
|
+
|
|
72
134
|
end
|
|
73
135
|
end
|
|
74
136
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "ace/support/cli"
|
|
5
|
+
require_relative "support"
|
|
6
|
+
require_relative "../../molecules/native_source"
|
|
7
|
+
require_relative "../../organisms/native_source_builder"
|
|
8
|
+
|
|
9
|
+
module Ace
|
|
10
|
+
module Herdr
|
|
11
|
+
module CLI
|
|
12
|
+
module Commands
|
|
13
|
+
class NativeSource < Ace::Support::Cli::Command
|
|
14
|
+
include Ace::Support::Cli::Base
|
|
15
|
+
include Runtime
|
|
16
|
+
|
|
17
|
+
desc "Describe, build, or verify the packaged guarded native source without running Herdr"
|
|
18
|
+
argument :operation, desc: "selection, build, or verify"
|
|
19
|
+
option :source, type: :string, desc: "Clean local Git checkout containing the pinned baseline"
|
|
20
|
+
option :output, type: :string, desc: "New build artifact directory"
|
|
21
|
+
option :target, type: :string, desc: "Selected Linux target"
|
|
22
|
+
option :artifact, type: :string, desc: "Build artifact directory to verify"
|
|
23
|
+
|
|
24
|
+
def initialize(selection: nil, builder: nil)
|
|
25
|
+
@selection = selection
|
|
26
|
+
@builder = builder
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def call(operation: nil, **options)
|
|
30
|
+
selection = @selection || Molecules::NativeSource.new
|
|
31
|
+
supplied = options.select { |key, value| %i[source output target artifact].include?(key) && !value.nil? }.keys
|
|
32
|
+
result = case operation
|
|
33
|
+
when "selection"
|
|
34
|
+
cli_error("selection accepts no build/artifact options") unless supplied.empty?
|
|
35
|
+
selection.description
|
|
36
|
+
when "verify"
|
|
37
|
+
cli_error("verify requires only --artifact") unless supplied == [:artifact] && !options[:artifact].empty?
|
|
38
|
+
selection.verify_artifact(directory: options.fetch(:artifact))
|
|
39
|
+
when "build"
|
|
40
|
+
unless supplied.sort == %i[output source target] && supplied.all? { |key| !options[key].empty? }
|
|
41
|
+
cli_error("build requires --source, --output and --target")
|
|
42
|
+
end
|
|
43
|
+
builder = @builder || Organisms::NativeSourceBuilder.new(selection: selection)
|
|
44
|
+
builder.build(source: options.fetch(:source), output: options.fetch(:output), target: options.fetch(:target))
|
|
45
|
+
else
|
|
46
|
+
cli_error("native-source operation must be selection, build, or verify")
|
|
47
|
+
end
|
|
48
|
+
puts JSON.generate(result)
|
|
49
|
+
rescue Molecules::NativeSource::Error => error
|
|
50
|
+
raise Ace::Support::Cli::Error, error.message
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/ace/herdr/cli.rb
CHANGED
|
@@ -15,6 +15,7 @@ require_relative "cli/commands/capture"
|
|
|
15
15
|
require_relative "cli/commands/workspace"
|
|
16
16
|
require_relative "cli/commands/tab"
|
|
17
17
|
require_relative "cli/commands/list_presets"
|
|
18
|
+
require_relative "cli/commands/native_source"
|
|
18
19
|
|
|
19
20
|
module Ace
|
|
20
21
|
module Herdr
|
|
@@ -29,6 +30,7 @@ module Ace
|
|
|
29
30
|
["deliver", "Push an answer to an agent pane (ace-hitl delivery contract)"],
|
|
30
31
|
["inbox", "Durable agent message queue and reconciliation"],
|
|
31
32
|
["dispatch", "Start an agent in one command: tab + agent + prompt"],
|
|
33
|
+
["native-source", "Describe, build, or verify the packaged guarded native source"],
|
|
32
34
|
["list", "List live panes, tabs, or workspaces as one JSON line"],
|
|
33
35
|
["send", "Send a command, raw text, or named keys to a pane"],
|
|
34
36
|
["capture", "Print recent pane output as raw text"],
|
|
@@ -69,6 +71,7 @@ module Ace
|
|
|
69
71
|
register "deliver", CLI::Commands::Deliver.new
|
|
70
72
|
register "inbox", CLI::Commands::Inbox.new
|
|
71
73
|
register "dispatch", CLI::Commands::Dispatch.new
|
|
74
|
+
register "native-source", CLI::Commands::NativeSource.new
|
|
72
75
|
register "list", CLI::Commands::List.new
|
|
73
76
|
register "send", CLI::Commands::Send.new
|
|
74
77
|
register "capture", CLI::Commands::Capture.new
|
data/lib/ace/herdr/errors.rb
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module Ace
|
|
4
4
|
module Herdr
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when the caller's herdr workspace/session/pane cannot be resolved
|
|
8
|
+
class TargetResolutionError < Error; end
|
|
9
|
+
|
|
10
|
+
# Raised when arguments or refs fail validation
|
|
11
|
+
class ValidationError < Error; end
|
|
12
|
+
|
|
13
|
+
# Raised when a bounded wait exceeds its timeout
|
|
14
|
+
class WaitTimeoutError < Error; end
|
|
15
|
+
|
|
16
|
+
# Raised when a preset tab was created natively but a later
|
|
17
|
+
# materialization step (splits, commands, agents) failed; carries the
|
|
18
|
+
# created tab id so the owner can roll back exactly that tab.
|
|
19
|
+
class TabMaterializationError < Error
|
|
20
|
+
attr_reader :tab_id
|
|
21
|
+
|
|
22
|
+
def initialize(tab_id:, message:)
|
|
23
|
+
@tab_id = tab_id
|
|
24
|
+
super(message)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
5
28
|
# Base for herdr CLI executor failures; retry classification drives the
|
|
6
29
|
# ace-hitl DeliverResult state mapping (retryable vs terminal).
|
|
7
30
|
class ExecutorError < Error
|