ace-herdr 0.3.2 → 0.5.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.
- checksums.yaml +4 -4
- data/.ace-defaults/herdr/config.yml +0 -4
- data/CHANGELOG.md +30 -0
- data/docs/usage.md +65 -32
- data/lib/ace/herdr/cli/commands/inbox.rb +81 -31
- data/lib/ace/herdr/cli/commands/native_source.rb +56 -0
- data/lib/ace/herdr/cli/commands/support.rb +2 -6
- data/lib/ace/herdr/cli.rb +3 -0
- data/lib/ace/herdr/errors.rb +23 -0
- data/lib/ace/herdr/molecules/bounded_process.rb +266 -51
- data/lib/ace/herdr/molecules/delivery_record_store.rb +81 -5
- 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 +259 -0
- 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/deliverer.rb +2 -3
- data/lib/ace/herdr/organisms/inbox.rb +384 -137
- 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/organisms/runtime_adapter.rb +30 -1
- data/lib/ace/herdr/version.rb +1 -1
- data/lib/ace/herdr.rb +5 -28
- metadata +69 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03bb080ee7010378787d8e6e05033330cddf0a03405f9bdb321f95b3021395b1
|
|
4
|
+
data.tar.gz: 7b935435b107aeafe1023139a526135131f600711800afbbde4f7cc6de61e5d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ad76243e13f3abc8656f2e6d443dc0d12e2b878849716fde11d4ca55152537f11595ff01e029e08b69c122e2863476e2fb734f733cfd0bfb91115e1758611630
|
|
7
|
+
data.tar.gz: 51735a6becc55d04ab97c0ee83ec735933705674d0fcf6a8c27833a219c9b71e7fa1e132733061f27d557b1914fc9aada318a9b06f09c7c43902ff339b185242
|
|
@@ -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,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.5.0] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- 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.
|
|
15
|
+
- Remove dedicated Codex app-server selection, message-read observation, signing keys and consumed/superseded reconciliation APIs.
|
|
16
|
+
- Keep authenticated original-context admission and actual scoped effects without a second current-state projection.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Load bounded process/error primitives directly and preserve bounded cleanup of the original process after execution uncertainty.
|
|
21
|
+
|
|
22
|
+
## [0.4.0] - 2026-10-05
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- Use the shared typed reverse-reference pair at CLI, Inbox and delivery boundaries, refusing malformed persisted targets and noncanonical serialized files before claim or native observation.
|
|
26
|
+
- Pin managed delivery to the accepted original native target under the event lock and reject secret-bearing payloads even without a nested message.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Declare the required direct dependencies and minimum producer versions for this coordinated release: `ace-hitl-contract ~> 0.2`, `ace-runtime ~> 0.2`.
|
|
31
|
+
- Bind protected control to the canonical per-attempt server/socket identity while retaining the fixed native executable view selected by installation verification.
|
|
32
|
+
- Validate and preserve the shared managed delivery envelope at Inbox enqueue, rejecting mismatched scope/digest/correlation and changed replay metadata.
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Add protected Herdr control pinned to the installed server/socket and fixed workspace, creating one fresh bootstrap pane with exact child observation and no default shell.
|
|
37
|
+
|
|
38
|
+
- Expose verified native process/session ownership and allow identical signed inbox proof re-verification after settlement without duplicate transitions.
|
|
39
|
+
|
|
10
40
|
## [0.3.2] - 2026-10-04
|
|
11
41
|
|
|
12
42
|
### Changed
|
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]`
|
|
@@ -48,45 +48,35 @@ Native `agent_blocked` becomes `SendRejectedError`; `agent_prompt_stalled` becom
|
|
|
48
48
|
|
|
49
49
|
Use a stable event ID for one message and the assignment attempt that owns it. The reverse-address JSON contains `session` and `pane`, as with `deliver`.
|
|
50
50
|
|
|
51
|
+
Reverse references use the shared `ace.hitl.ref/v1` pair contract. Herdr token names
|
|
52
|
+
remain valid; the codec also preserves paired native tmux IDs (`$0`/`%0`) when
|
|
53
|
+
carried by managed metadata. Mixed native/token or cross-field IDs refuse.
|
|
54
|
+
Wire and persisted references must be canonical. Syntax acceptance does not add
|
|
55
|
+
tmux execution to this Herdr controller or replace its exact native ownership,
|
|
56
|
+
target and submission checks.
|
|
57
|
+
|
|
51
58
|
```bash
|
|
52
59
|
ace-herdr inbox enqueue --event inb-12345678 --attempt ATTEMPT --ref ref.json --file prompt.txt
|
|
53
60
|
ace-herdr inbox status --event inb-12345678 --format json
|
|
54
61
|
ace-herdr inbox deliver --event inb-12345678
|
|
55
62
|
```
|
|
56
63
|
|
|
57
|
-
`enqueue` checks the live pane,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
ace-herdr inbox reconcile --event inb-12345678 --receipt proof.json
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
The receipt is a file supplied from an operator or supervisor observation of the native outcome. Codex and Pi expose submission but no queryable consumption or eviction proof, so `reconcile` never polls the native queue or infers an outcome from age. Before enqueue, the supervisor sets `inbox_receipt_public_key` in `.ace/herdr/config.yml` to the absolute path of its trusted RSA public key. Each event pins that key's fingerprint; a later process cannot substitute a different key. Protect this configuration from delivery requesters. The receipt file must have a detached SHA-256 RSA signature at `FILE.sig`. The private key stays with the operator or supervisor. Its JSON must include the recorded event, attempt, claim generation, payload digest, and complete binding, plus an outcome (`consumed` or `superseded`), an identified observer, and a native observation reference:
|
|
66
|
-
|
|
67
|
-
```json
|
|
68
|
-
{
|
|
69
|
-
"event_id": "inb-12345678",
|
|
70
|
-
"attempt_id": "ATTEMPT",
|
|
71
|
-
"claim_generation": 1,
|
|
72
|
-
"payload_sha256": "<digest from inbox status>",
|
|
73
|
-
"binding": {"session": "<exact binding from inbox status>"},
|
|
74
|
-
"outcome": "consumed",
|
|
75
|
-
"observer": {"role": "operator", "id": "operator-id"},
|
|
76
|
-
"evidence": {"kind": "consumed_acknowledged", "native_reference": "session-log:42", "observation": "message consumed and acknowledged"}
|
|
77
|
-
}
|
|
78
|
-
```
|
|
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.
|
|
79
68
|
|
|
80
|
-
|
|
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.
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
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.
|
|
90
80
|
|
|
91
81
|
## tmux-intent ↔ herdr-command parity
|
|
92
82
|
|
|
@@ -364,3 +354,46 @@ Commands raise a CLI error (non-zero exit) carrying herdr's machine code where o
|
|
|
364
354
|
- Invalid send shapes: usage error **before any transport call** (nothing is sent)
|
|
365
355
|
|
|
366
356
|
Exit `0` means success (including an explicit empty `list` result or a satisfied wait).
|
|
357
|
+
|
|
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.
|
|
398
|
+
|
|
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,29 +17,43 @@ 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"
|
|
35
|
-
|
|
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
|
|
54
|
+
inbox = Organisms::Inbox.from_config(config: config, executor: executor,
|
|
36
55
|
native: @native || Molecules::NativeQueueExecutor.new,
|
|
37
|
-
|
|
38
|
-
receipt_public_key: %w[enqueue reconcile].include?(operation) ? receipt_public_key : nil)
|
|
56
|
+
root: Dir.pwd)
|
|
39
57
|
result = case operation
|
|
40
58
|
when "enqueue"
|
|
41
59
|
%i[attempt ref file].each { |key| cli_error("--#{key} is required") if options[key].to_s.empty? }
|
|
@@ -45,24 +63,8 @@ module Ace
|
|
|
45
63
|
inbox.status(event: options[:event])
|
|
46
64
|
when "deliver"
|
|
47
65
|
inbox.deliver(event: options[:event])
|
|
48
|
-
when "reconcile"
|
|
49
|
-
path = options[:receipt]
|
|
50
|
-
begin
|
|
51
|
-
bytes = path.to_s.empty? ? nil : File.binread(path)
|
|
52
|
-
receipt = bytes && JSON.parse(bytes)
|
|
53
|
-
signature = bytes && File.binread("#{path}.sig")
|
|
54
|
-
rescue SystemCallError, JSON::ParserError => e
|
|
55
|
-
# Receipt input is caller-owned: unreadable or malformed
|
|
56
|
-
# proof files surface as the documented refusal, never as
|
|
57
|
-
# command failures.
|
|
58
|
-
puts JSON.generate(inbox.status(event: options[:event]).merge(
|
|
59
|
-
"reconciliation_refusal" => "invalid receipt: #{e.message}"))
|
|
60
|
-
return
|
|
61
|
-
end
|
|
62
|
-
inbox.reconcile(event: options[:event], receipt: receipt,
|
|
63
|
-
signed_bytes: bytes, signature: signature)
|
|
64
66
|
else
|
|
65
|
-
cli_error("operation must be enqueue, status,
|
|
67
|
+
cli_error("operation must be enqueue, status, or deliver")
|
|
66
68
|
end
|
|
67
69
|
puts JSON.generate(result)
|
|
68
70
|
end
|
|
@@ -70,17 +72,65 @@ module Ace
|
|
|
70
72
|
raise Ace::Support::Cli::Error, e.message
|
|
71
73
|
end
|
|
72
74
|
|
|
75
|
+
|
|
73
76
|
private
|
|
74
77
|
|
|
75
|
-
def
|
|
76
|
-
|
|
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
|
|
78
126
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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))})
|
|
83
132
|
end
|
|
133
|
+
|
|
84
134
|
end
|
|
85
135
|
end
|
|
86
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
|
|
@@ -30,12 +30,8 @@ module Ace
|
|
|
30
30
|
# fail closed (spec 8wm.t.vrz §3)
|
|
31
31
|
def resolve_ref(session, pane)
|
|
32
32
|
Ace::Hitl::Providers::Ref.new(
|
|
33
|
-
session:
|
|
34
|
-
|
|
35
|
-
),
|
|
36
|
-
pane: Ace::Hitl::Providers::Ref.validate!(
|
|
37
|
-
pane || ENV["HERDR_PANE"], "HERDR_PANE"
|
|
38
|
-
)
|
|
33
|
+
session: session || ENV["HERDR_SESSION"], pane: pane || ENV["HERDR_PANE"],
|
|
34
|
+
session_source: "HERDR_SESSION", pane_source: "HERDR_PANE"
|
|
39
35
|
)
|
|
40
36
|
end
|
|
41
37
|
|
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
|