ace-runtime 0.4.0 → 0.4.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/CHANGELOG.md +6 -0
- data/README.md +34 -20
- data/docs/usage.md +23 -126
- data/lib/ace/runtime/molecules/execution_unit_installation.rb +1 -1
- data/lib/ace/runtime/version.rb +1 -1
- data/native/README.md +13 -124
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b9c53bb970a918797a57b7aa00c36587a8da5420944cca83cee41a6f6ae2792
|
|
4
|
+
data.tar.gz: 4c4486463938f67ce14882472ede27fd96016a95c92a60d38a85fa60e5e87027
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b02211e6604c9b51ea8e2e294dac5678cad47b52f0b1b1e5fc971888bed07876974a16a3eb0178b5d4551f274b2559aba374e4ec8c578121a99ec6f888b0795
|
|
7
|
+
data.tar.gz: f36be2a74247d133b118cdfdbe58e24628f8051b262848e171523a6897c4bfd99d503cdac0de5c87f182c4c2179051ca4c01f64ed71a2fda3b310ba83496ca05
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.1] - 2026-10-18
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Validate execution-scope unit names with the existing systemd unit shape, `.slice`/`.service` suffix and effective isolation checks only. A unit name that contains a role word such as `overseer` is no longer rejected: a role label is not a security boundary, and the real checks stay unchanged.
|
|
14
|
+
|
|
9
15
|
## [0.4.0] - 2026-10-16
|
|
10
16
|
|
|
11
17
|
### Added
|
data/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# ace-runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **State owner:** none. `ace-runtime` is a stateless terminal intent contract;
|
|
4
|
+
> tmux/Herdr adapters own only terminal transport. Task, assignment, and HITL
|
|
5
|
+
> state belong to `.ace-tasks/` and the local stores under `.ace-local/`
|
|
6
|
+
> ([docs/lab-flow.md](../docs/lab-flow.md)).
|
|
7
|
+
|
|
8
|
+
Runtime-neutral terminal intent contract for ACE. One
|
|
4
9
|
duck-typed intent API that terminal runtimes implement and all consumers
|
|
5
|
-
call
|
|
10
|
+
call -- the structural change that makes herdr an equal partner to tmux
|
|
6
11
|
instead of a side gem.
|
|
7
12
|
|
|
8
13
|
Adapters live INSIDE the existing wrapper gems: `ace-tmux` and `ace-herdr`
|
|
@@ -17,22 +22,22 @@ Eleven operations, keyword-arg surfaces mirroring today's consumer calls:
|
|
|
17
22
|
| Operation | Surface |
|
|
18
23
|
|-----------|---------|
|
|
19
24
|
| Context | `runtime.context` → `{in_runtime:, session:, window:, pane:}` |
|
|
20
|
-
| Ensure window | `runtime.ensure_window(name:, root:, preset: nil)`
|
|
25
|
+
| Ensure window | `runtime.ensure_window(name:, root:, preset: nil)` -- idempotent by normalized name; conflicting root/preset raises `WindowConflictError` |
|
|
21
26
|
| Process identity | `runtime.process_binding(pane:, caller_pid:)` → verified owner/native binding or nil (unknown); read-only, no authorization |
|
|
22
|
-
| Prepare pane | `runtime.prepare_pane(window:)`
|
|
27
|
+
| Prepare pane | `runtime.prepare_pane(window:)` -- splits when needed; target stays alive after a submitted command exits |
|
|
23
28
|
| Focus | `runtime.focus(window:)` |
|
|
24
|
-
| Send | `runtime.send(pane:, command: nil, items: [])`
|
|
29
|
+
| Send | `runtime.send(pane:, command: nil, items: [])` -- ordered `{message: String}` / `{key: String}` entries |
|
|
25
30
|
| Capture | `runtime.capture(pane:, lines: 40)` → text |
|
|
26
31
|
| Wait output | `runtime.wait_output(pane:, pattern:, timeout:)` |
|
|
27
32
|
| Wait agent | `runtime.wait_agent(pane:, states:, timeout:)` |
|
|
28
|
-
| Wait lifecycle | `runtime.wait_lifecycle(condition:, target:, timeout:)`
|
|
33
|
+
| Wait lifecycle | `runtime.wait_lifecycle(condition:, target:, timeout:)` -- `window-exits`/`window-active`/`pane-exists`/`pane-exited` observations only |
|
|
29
34
|
| Close | `runtime.close_window(window:)` |
|
|
30
35
|
| List | `runtime.list_windows` / `runtime.list_panes(window:)` |
|
|
31
36
|
|
|
32
37
|
Convenience shapes: `send_command(pane:, command:)`,
|
|
33
38
|
`send_text(pane:, text:)`, `send_keys(pane:, keys:)`.
|
|
34
39
|
|
|
35
|
-
Target identity is opaque adapter-owned handles at every boundary
|
|
40
|
+
Target identity is opaque adapter-owned handles at every boundary -- the
|
|
36
41
|
contract never predicts or formats pane ids (tmux `%id` and herdr
|
|
37
42
|
`w1:p1` stay adapter-internal). Window operations take the (sanitized)
|
|
38
43
|
window name; pane operations take the handle returned by the adapter.
|
|
@@ -52,30 +57,33 @@ CLI/runtime selection precedence (highest wins):
|
|
|
52
57
|
|
|
53
58
|
1. explicit name (`ace-runtime send --runtime herdr`)
|
|
54
59
|
2. `ACE_RUNTIME` environment (consumer forks set this plus target context)
|
|
55
|
-
3. configured runtime
|
|
60
|
+
3. configured runtime -- `.ace/runtime/config.yml` key `runtime`
|
|
56
61
|
4. auto-detection from the environment (`TMUX` / `ACE_TMUX_SESSION`, or
|
|
57
62
|
`HERDR_SESSION` + `HERDR_PANE`). When both are live, tmux wins
|
|
58
63
|
(existing default). Only assign auto launch mode selects headless
|
|
59
64
|
outside any runtime; that decision lives above this contract.
|
|
60
65
|
|
|
61
66
|
An explicitly selected runtime that turns out unknown or unavailable
|
|
62
|
-
fails closed
|
|
67
|
+
fails closed -- never a silent switch or headless downgrade.
|
|
63
68
|
|
|
64
69
|
## Send matrix
|
|
65
70
|
|
|
66
71
|
`send` is the only submission primitive; the granular ops are shapes of
|
|
67
|
-
it. `items` is an ORDERED list
|
|
72
|
+
it. `items` is an ORDERED list -- the only shape that preserves
|
|
68
73
|
message/key interleaving. When `command` is present, `items` holds
|
|
69
74
|
exclusively post-command keys; leading keys are a usage error before any
|
|
70
75
|
transport call (the CLI enforces `--cmd` before every `--key`).
|
|
71
76
|
|
|
72
77
|
- **Plain-pane adapters** (`send_profile == :plain_pane`, e.g. tmux):
|
|
78
|
+
|
|
73
79
|
messages are raw text (no implicit submission), keys are keystrokes,
|
|
74
80
|
each Enter submits pending text; `command` submits once, then trailing
|
|
75
81
|
keys deliver.
|
|
82
|
+
|
|
76
83
|
- **Agent-aware adapters** (`send_profile == :agent_aware`, e.g. herdr):
|
|
84
|
+
|
|
77
85
|
`command` or the concatenated messages become ONE prompt that submits
|
|
78
|
-
itself (message-only input DOES submit once on agent panes
|
|
86
|
+
itself (message-only input DOES submit once on agent panes -- intended
|
|
79
87
|
divergence); at most one trailing Enter is dropped and reported via
|
|
80
88
|
the result; keys-only sequences go to agent keys; every other
|
|
81
89
|
interleaving is rejected before any transport call.
|
|
@@ -87,16 +95,18 @@ profiles.
|
|
|
87
95
|
|
|
88
96
|
All failures subclass `Ace::Runtime::Error`:
|
|
89
97
|
|
|
90
|
-
- `UnknownRuntimeError`
|
|
91
|
-
- `RuntimeUnavailableError`
|
|
92
|
-
- `TargetNotFoundError`
|
|
93
|
-
- `WindowConflictError`
|
|
94
|
-
- `SendRejectedError`
|
|
95
|
-
- `SendStalledError`
|
|
98
|
+
- `UnknownRuntimeError` -- with the sorted available list
|
|
99
|
+
- `RuntimeUnavailableError` -- known runtime, unusable right now
|
|
100
|
+
- `TargetNotFoundError` -- window/pane target does not exist
|
|
101
|
+
- `WindowConflictError` -- same normalized name, incompatible root/preset
|
|
102
|
+
- `SendRejectedError` -- pre-send rejection; terminal, no transport call
|
|
103
|
+
- `SendStalledError` -- submission accepted but the target never started
|
|
104
|
+
|
|
96
105
|
processing; OUTCOME UNCERTAIN, callers must not auto-resend
|
|
97
|
-
- `WaitTimeoutError` — a wait exceeded its deadline (seconds context)
|
|
98
106
|
|
|
99
|
-
|
|
107
|
+
- `WaitTimeoutError` -- a wait exceeded its deadline (seconds context)
|
|
108
|
+
|
|
109
|
+
`pane-exited` is a runtime observation only -- never assignment success,
|
|
100
110
|
receipt confirmation, or prune authorization.
|
|
101
111
|
|
|
102
112
|
## Adapter contract
|
|
@@ -110,12 +120,16 @@ Ace::Runtime.register(:<name>, -> { Ace::Tmux::RuntimeAdapter.new })
|
|
|
110
120
|
```
|
|
111
121
|
|
|
112
122
|
The contract resolves an unregistered known name by attempting to load
|
|
113
|
-
that entrypoint lazily
|
|
123
|
+
that entrypoint lazily -- no gem dependency in either direction.
|
|
114
124
|
|
|
115
125
|
The packaged shared suite `Ace::Runtime::Testing::AdapterContract` is
|
|
116
126
|
the acceptance bar for ANY adapter. See `docs/usage.md` for the
|
|
117
127
|
adapter-authoring walkthrough.
|
|
118
128
|
|
|
129
|
+
> **Planned (L2):** retirement or redesign of the legacy native bootstrap
|
|
130
|
+
> artifacts under `native/` -- not available yet. They are not required by the
|
|
131
|
+
> file-based flow.
|
|
132
|
+
|
|
119
133
|
Note: adapters intentionally define `send` (the intent operation),
|
|
120
134
|
which overrides `Object#send`. Use `__send__` for reflective dispatch
|
|
121
135
|
on adapter objects.
|
data/docs/usage.md
CHANGED
|
@@ -30,15 +30,22 @@ ace-runtime send --pane %1 --cmd 'vim .' --key C-w
|
|
|
30
30
|
Rules enforced before any transport call:
|
|
31
31
|
|
|
32
32
|
- `--pane` is required; at least one of `--cmd`, `--msg`, `--key` is
|
|
33
|
+
|
|
33
34
|
required (none = usage error).
|
|
34
|
-
|
|
35
|
+
|
|
36
|
+
- `--key` before `--cmd` is a usage error -- `--cmd` must be declared
|
|
37
|
+
|
|
35
38
|
before every key. Keys declared after `--cmd` are post-submission
|
|
36
39
|
keystrokes.
|
|
40
|
+
|
|
37
41
|
- The CLI builds direct-API items as all supplied messages followed by
|
|
42
|
+
|
|
38
43
|
all supplied keys. Direct Ruby callers use `send(items:)` when they
|
|
39
44
|
need arbitrary interleaving (for example `msg, key, msg` on plain
|
|
40
45
|
panes).
|
|
46
|
+
|
|
41
47
|
- On agent panes (`:agent_aware` profile) the callback shapes submit
|
|
48
|
+
|
|
42
49
|
exactly once: `--msg ... --key Enter` becomes one self-submitting
|
|
43
50
|
prompt with the trailing Enter dropped and reported; interleaved
|
|
44
51
|
items, multiple Enters, `--cmd` with messages, and `--cmd` with
|
|
@@ -87,7 +94,7 @@ name)`; an existing window with an incompatible root or preset raises
|
|
|
87
94
|
`WindowConflictError` instead of silently reusing the wrong worktree.
|
|
88
95
|
|
|
89
96
|
`wait_lifecycle` accepts only `window-exists`, `window-active`,
|
|
90
|
-
`pane-exists`, `pane-exited`. It reports the condition only
|
|
97
|
+
`pane-exists`, `pane-exited`. It reports the condition only -- never
|
|
91
98
|
authorizes completion, receipts, or pruning. `pane-exited` in
|
|
92
99
|
particular is an observation, not assignment success or safe prune
|
|
93
100
|
evidence.
|
|
@@ -105,14 +112,14 @@ evidence.
|
|
|
105
112
|
|
|
106
113
|
Explicit selections never downgrade: unknown names fail closed with
|
|
107
114
|
the available list (`UnknownRuntimeError`); a selected-but-unavailable
|
|
108
|
-
runtime raises `RuntimeUnavailableError`
|
|
115
|
+
runtime raises `RuntimeUnavailableError` -- no headless fallback, no
|
|
109
116
|
silent switch. The assign auto launch mode is the only consumer that
|
|
110
117
|
selects headless outside any runtime, and it does so above this
|
|
111
118
|
contract.
|
|
112
119
|
|
|
113
120
|
## Adapter authoring
|
|
114
121
|
|
|
115
|
-
Adapters are duck-typed
|
|
122
|
+
Adapters are duck-typed -- no base class (ace-hitl provider-registry
|
|
116
123
|
pattern). An adapter package:
|
|
117
124
|
|
|
118
125
|
1. declares a dependency on `ace-runtime` (never the reverse),
|
|
@@ -140,11 +147,16 @@ Non-negotiables enforced by the contract suite:
|
|
|
140
147
|
|
|
141
148
|
- target handles stay opaque at every public boundary,
|
|
142
149
|
- `prepare_pane` yields a writable live shell/agent target retained
|
|
150
|
+
|
|
143
151
|
after a submitted command exits (bare command panes do not satisfy
|
|
144
152
|
preparation),
|
|
153
|
+
|
|
145
154
|
- `detect` and adapter availability checks are side-effect-free; an
|
|
155
|
+
|
|
146
156
|
unavailable explicit runtime never becomes headless,
|
|
157
|
+
|
|
147
158
|
- installed tests must verify both runtimes; never assert untested
|
|
159
|
+
|
|
148
160
|
behavior.
|
|
149
161
|
|
|
150
162
|
### The acceptance bar
|
|
@@ -170,7 +182,7 @@ class TmuxAdapterContractTest < AceTestCase
|
|
|
170
182
|
end
|
|
171
183
|
```
|
|
172
184
|
|
|
173
|
-
The suite drives your adapter over `ScriptedRuntime`
|
|
185
|
+
The suite drives your adapter over `ScriptedRuntime` -- an in-memory
|
|
174
186
|
terminal (windows, panes, raw text writes, named keys, capture, agent
|
|
175
187
|
state) that records every mutation op so the suite can assert delivery
|
|
176
188
|
ordering and rejection-before-transport. Wire your adapter's native
|
|
@@ -188,125 +200,10 @@ in the ace-runtime gem; the in-gem suite
|
|
|
188
200
|
(`test/contract/adapter_contract_test.rb`) proves the battery against
|
|
189
201
|
it for both send profiles.
|
|
190
202
|
|
|
191
|
-
##
|
|
192
|
-
|
|
193
|
-
The source-only `Ace::Runtime::Molecules::NetworkInstallationEvidence` consumer
|
|
194
|
-
checks the protected content selected by the installed scope owner:
|
|
195
|
-
|
|
196
|
-
```ruby
|
|
197
|
-
result = Ace::Runtime::Molecules::NetworkInstallationEvidence.verify!(
|
|
198
|
-
selection: boundary_manifest.fetch("network_installation"),
|
|
199
|
-
expected: {
|
|
200
|
-
"slot_id" => slot_id,
|
|
201
|
-
"namespace_path" => installed_namespace_path,
|
|
202
|
-
"boot_id" => current_boot_id,
|
|
203
|
-
"namespace_identity" => {"device" => pinned_device, "inode" => pinned_inode},
|
|
204
|
-
"installer_artifact_sha256" => installed_producer_sha256
|
|
205
|
-
}
|
|
206
|
-
)
|
|
207
|
-
```
|
|
203
|
+
## Native bootstrap and network evidence (legacy)
|
|
208
204
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
the owner validates its type/current boot and keeps the namespace pinned through
|
|
215
|
-
admission. This verifier reads content artifacts and never opens/substitutes the
|
|
216
|
-
namespace, probes traffic or grants runtime networking privilege.
|
|
217
|
-
|
|
218
|
-
Successful output contains exactly `report_id`, `boot_id`, `slot_id`,
|
|
219
|
-
`namespace_path`, `namespace_identity`, `profile_sha256`, `policy_export_sha256`,
|
|
220
|
-
`report_sha256` and `installer_artifact_sha256`, deeply frozen. It authenticates
|
|
221
|
-
installation content, not actual enforcement from a summary boolean. Missing,
|
|
222
|
-
unreadable, malformed, conflicting, oversized or context-mismatched evidence
|
|
223
|
-
raises `Ace::Runtime::RuntimeUnavailableError`; the authority boundary exposes
|
|
224
|
-
that refusal as `evidence_unavailable` and retains public `invalid_input`
|
|
225
|
-
classification for malformed public selectors. No absent-evidence fallback.
|
|
226
|
-
|
|
227
|
-
The reader pins regular-file and directory descriptors for the complete graph,
|
|
228
|
-
checks exact lengths/digests and revalidates original objects/modes at completion.
|
|
229
|
-
It uses root ownership and no group/other write on supported Linux POSIX local
|
|
230
|
-
filesystems (`ext2`, `ext3`, `ext4`, `xfs`, `btrfs`, `tmpfs`): group mode bits
|
|
231
|
-
represent the access ACL mask, so named non-root ACL users/groups cannot obtain
|
|
232
|
-
write through it. Unknown filesystem models refuse. All ancestors are checked;
|
|
233
|
-
no symlink, untrusted writable ancestor or mutable worker artifact qualifies.
|
|
234
|
-
Content bounds are 1MiB per artifact, 16MiB aggregate and 256 distinct artifacts.
|
|
235
|
-
Trusted installer code verifying a larger explicitly selected source closure can
|
|
236
|
-
construct `ProtectedArtifactSet.new(count_limit: 4096)`; the value must be a
|
|
237
|
-
positive integer supplied by trusted code, never copied from an artifact or
|
|
238
|
-
request. Existing `file_limit:` and `total_limit:` budgets still apply. Mixed
|
|
239
|
-
`read!`/`read_path!` calls share the count; repeating a retained reference spends
|
|
240
|
-
no additional entry, and the first new artifact beyond the limit refuses.
|
|
241
|
-
Trusted source installers can use `with_readonly_handle!(reference)` inside an
|
|
242
|
-
active `ProtectedArtifactSet#with` callback to hand off the authenticated retained
|
|
243
|
-
regular file. It yields a read-only duplicate without reopening the selected
|
|
244
|
-
pathname, verifies the held set before and after the callback, and closes the
|
|
245
|
-
duplicate on success or error. No descriptor or maintenance authority survives
|
|
246
|
-
the callback. Existing reference/budget/root protection rules still apply.
|
|
247
|
-
|
|
248
|
-
Network evidence verifiers retain their default budgets. Held descriptors also
|
|
249
|
-
require sufficient process file-descriptor capacity in the installer environment.
|
|
250
|
-
The profile's 256-entry bound counts its four outer collections only; nested
|
|
251
|
-
ranges, ports and helper/configuration refs are bounded by the profile bytes and
|
|
252
|
-
separate artifact graph limits. Strict JSON parsing requires the declared
|
|
253
|
-
`json >= 2.20, < 3` dependency to reject duplicate keys and comments explicitly.
|
|
254
|
-
|
|
255
|
-
The trusted domain installer owns complete effective policy/topology comparison,
|
|
256
|
-
independent correlated flow checks, immutable digest-bearing evidence filenames
|
|
257
|
-
and publication that preserves historical artifacts. The generic reference
|
|
258
|
-
schema specifies no basename grammar: this consumer checks selected content and
|
|
259
|
-
protection, and cannot establish publication history. Raw policy/topology/setup
|
|
260
|
-
and correlated observations remain required; unsupported or incomplete domain
|
|
261
|
-
checks must never produce a passing report. Domain source/installed review proves
|
|
262
|
-
those obligations. For each loopback tool, the fixed domain procedure inspects
|
|
263
|
-
the actual selected executable against its profile `artifact_sha256` and retains
|
|
264
|
-
the observation in the exact tool-target trace. The schema provides no executable
|
|
265
|
-
path/ref: generic ACE authenticates the trace/raw refs and digest format; it does
|
|
266
|
-
not compare a tool executable digest with observation bytes or invent a ref.
|
|
267
|
-
Domain acceptance must exercise tool-executable mismatch refusal.
|
|
268
|
-
|
|
269
|
-
Root installation lifetime, private admission CAS, stage joins,
|
|
270
|
-
namespace containment and maintenance ordering belong to their respective
|
|
271
|
-
installed owners; this verifier does not implement them or supply native,
|
|
272
|
-
packet-filter or installed acceptance by itself.
|
|
273
|
-
|
|
274
|
-
### Original boot proof and fixed live readiness views
|
|
275
|
-
|
|
276
|
-
`ExecutionBootBaseline#select!(expected:)` authenticates the fixed protected
|
|
277
|
-
per-slot publication pointer at provisioning and returns its immutable proof
|
|
278
|
-
reference. `#verify!(selection:, expected:)` reads only that original reference,
|
|
279
|
-
joining whole-file bytes, slot, original boot, mapping digest, root PID1 birth
|
|
280
|
-
context and exact original network installer artifact. It never discovers an old
|
|
281
|
-
namespace or substitutes a current pointer. Proof JSON is closed and at most
|
|
282
|
-
16KiB. Trusted host capture, per-boot refresh and cold-start admission inhibition
|
|
283
|
-
remain domain gad.8/gad.b producer obligations.
|
|
284
|
-
|
|
285
|
-
`ServerResourceObservation` uses the same-UID native server's pinned root and
|
|
286
|
-
namespace descriptors without entering or changing a namespace. It reports the
|
|
287
|
-
complete mount table (at most256 rows), fixed API/tmp view mount IDs obtained from
|
|
288
|
-
contained O_PATH descriptors, server/hook IPC identities and the exact authority
|
|
289
|
-
socket identity. It also holds the original server’s `/proc/<pid>/ns/net` object,
|
|
290
|
-
requires the nsfs network-namespace type and rechecks its identity after collection.
|
|
291
|
-
The report includes that observed `network_namespace_identity`; it never substitutes
|
|
292
|
-
the hook’s namespace or a configured namespace identity. The private report shares
|
|
293
|
-
a65,536-byte ceiling. Mount path
|
|
294
|
-
prefixes, typed isolation flags and filesystem names alone do not prove live
|
|
295
|
-
isolation. `KernelViewTopology` verifies the fixed supported profile, read-only
|
|
296
|
-
API views, private IPC host difference and exact writable original backing;
|
|
297
|
-
unknown writable aliases and even declared writable API descendants refuse.
|
|
298
|
-
|
|
299
|
-
The fixed unit exposes only the selected read-only source-equals-view authority
|
|
300
|
-
socket and has `BindLogSockets=false`. Protected endpoint type/owner/incarnation,
|
|
301
|
-
contained view identity and authenticated peer remain separate required joins.
|
|
302
|
-
These observations supply admission evidence, not terminality or installed
|
|
303
|
-
effectiveness.
|
|
304
|
-
The supported devpts profile additionally requires an authenticated per-slot
|
|
305
|
-
`newinstance` backing, distinct from original host devpts. The trusted boot
|
|
306
|
-
baseline pins original host and selected instance identities plus the exact host
|
|
307
|
-
`pts/ptmx` link. The unit binds the selected root at `/dev/pts` and its read-only
|
|
308
|
-
ptmx node at `/dev/pts/ptmx`; the contained observer verifies the unchanged link,
|
|
309
|
-
character device and actual mount identity. `PrivateDevices` alone cannot prove
|
|
310
|
-
this isolation. Shared host RO/RW views and another slot's backing refuse.
|
|
311
|
-
gad.8 provisioning and gad.b refresh must deliver the selected source before
|
|
312
|
-
activation; controlled tests do not establish installed effectiveness.
|
|
205
|
+
> The native Linux worker bootstrap, the root-produced network installation
|
|
206
|
+
> evidence, the boot baseline and the kernel view topology readers belong to the
|
|
207
|
+
> replaced protected model. They are not part of the file-based flow and no
|
|
208
|
+
> command, task, or assignment requires them. Their removal/redesign is tracked
|
|
209
|
+
> in L1/L2; treat this package's runtime contract above as the supported surface.
|
|
@@ -299,7 +299,7 @@ module Ace
|
|
|
299
299
|
%w[root_directory runtime_directory network_namespace_path].all? { |key| path?(scope[key]) } &&
|
|
300
300
|
scope.fetch("runtime_directory").start_with?("/run/") && !overlaps?(scope.fetch("root_directory"), scope.fetch("runtime_directory")) &&
|
|
301
301
|
{"slice_unit" => ".slice", "service_unit" => ".service"}.all? { |key, suffix|
|
|
302
|
-
scope[key].is_a?(String) && SystemdScopeManager::UNIT.match?(scope[key]) && scope[key].end_with?(suffix)
|
|
302
|
+
scope[key].is_a?(String) && SystemdScopeManager::UNIT.match?(scope[key]) && scope[key].end_with?(suffix)
|
|
303
303
|
} &&
|
|
304
304
|
scope["unit_manifest_sha256"] == service.fetch("unit_manifest").fetch("sha256") &&
|
|
305
305
|
scope["boundary_manifest_sha256"] == service.fetch("boundary_manifest").fetch("sha256") &&
|
data/lib/ace/runtime/version.rb
CHANGED
data/native/README.md
CHANGED
|
@@ -1,124 +1,13 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
The `ace-runtime` gem ships `native/worker_gate.c` and
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
`
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
These libraries and their loader search directories must remain root-owned and
|
|
15
|
-
non-worker-writable. The pinned Herdr service starts with a clean root-controlled
|
|
16
|
-
environment, so no worker loader/preload environment reaches bootstrap startup.
|
|
17
|
-
|
|
18
|
-
From the installed gem directory, in a private unprivileged build directory:
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
native/build-worker-gate /absolute/private/build/ace-worker-gate
|
|
22
|
-
sha256sum /absolute/private/build/ace-worker-gate
|
|
23
|
-
readelf -h -d /absolute/private/build/ace-worker-gate
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
The administrator installs this exact artifact, without setuid/setgid bits:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
install -o root -g root -m 0755 /absolute/private/build/ace-worker-gate /usr/libexec/ace-worker-gate
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
All installation ancestors are root-owned and non-worker-writable. The fixed
|
|
33
|
-
root-owned `/etc/ace/assignment-authorities.json` records `bootstrap` and its exact
|
|
34
|
-
`bootstrap_sha256`. ACE verifies digest, ELF architecture, executable permissions,
|
|
35
|
-
ownership and ancestry before reservation. The native layout uses only this
|
|
36
|
-
absolute executable, installed mapping ID and correlation ticket; cwd and environment come from the root-controlled mapping after authority release.
|
|
37
|
-
The mandatory closed `worker_entry` pins interpreter and wrapper references; arbitrary
|
|
38
|
-
worker argv and shebang/PATH execution are not admitted.
|
|
39
|
-
The map grants no UID switching and the binary is never privileged.
|
|
40
|
-
|
|
41
|
-
The root-installed authority, launcher and native server units require:
|
|
42
|
-
|
|
43
|
-
```ini
|
|
44
|
-
User=<their distinct mapped account>
|
|
45
|
-
NoNewPrivileges=yes
|
|
46
|
-
CapabilityBoundingSet=
|
|
47
|
-
AmbientCapabilities=
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
Worker children inherit the server's empty bounds and no-new-privileges policy.
|
|
51
|
-
ACE checks all real/effective/saved/filesystem IDs, supplementary groups, every
|
|
52
|
-
capability set and `NoNewPrivs` from kernel status for all mapped participants,
|
|
53
|
-
including the pinned server. CAP_DAC_OVERRIDE/CAP_SETUID are refused, as is an
|
|
54
|
-
exec context that could gain capabilities or setuid privilege. Yama=2 remains
|
|
55
|
-
mandatory; an empty capability set alone does not stop same-UID injection.
|
|
56
|
-
|
|
57
|
-
The bootstrap sets non-dumpable before connecting, requires enforced Yama
|
|
58
|
-
`ptrace_scope=2`, requires empty Inh/Prm/Eff/Bnd/Amb capability sets and NoNewPrivs=1, verifies
|
|
59
|
-
worker IDs/groups and its retained actual kernel parent birth, authenticates authority
|
|
60
|
-
SO_PEERCRED and protected socket ancestry, and waits at most 30 seconds on one
|
|
61
|
-
stream. It never reconnects or executes after pre-release EOF, malformed input
|
|
62
|
-
or timeout. A committed release whose response is lost remains potentially
|
|
63
|
-
executed; public inspection must retain ownership rather than resend release.
|
|
64
|
-
|
|
65
|
-
The gate accepts only the maintained `ace.assign.authorities/v2` mapping. It
|
|
66
|
-
captures its actual parent PID and kernel boot/start-tick birth before reading
|
|
67
|
-
the map, checks that same birth before and after parent credentials, and repeats
|
|
68
|
-
the check before authority admission and immediately before exec. The canonical
|
|
69
|
-
`gate_ready` authority owner associates the actual gate peer and its parent with
|
|
70
|
-
the original recorded child/server; the map supplies no `server_identity`.
|
|
71
|
-
Changed parent birth, reparenting, inaccessible credentials or changed policy
|
|
72
|
-
refuses. The original retained mapping supplies `worker_entry={interpreter:REF,wrapper:REF}`
|
|
73
|
-
in the authenticated release, rather than selecting code from the current map.
|
|
74
|
-
Interpreter references are bounded to 32MiB and wrapper references to 1MiB.
|
|
75
|
-
Both root-protected regular held files are hashed and their metadata rechecked.
|
|
76
|
-
The interpreter must be an executable native ELF. The verified wrapper is copied
|
|
77
|
-
into a memfd sealed against writes/growth/shrinkage, reopened read-only, and the
|
|
78
|
-
writable descriptor dropped. Selected held interpreter FD4 and code FD5 survive
|
|
79
|
-
`fexecve(4, ["/proc/self/fd/4", "-I", "-S", "-B", "/proc/self/fd/5", "authority", "worker"], env)`.
|
|
80
|
-
Inherited stdin/stdout/stderr remain untouched; no provider lifetime timeout is added.
|
|
81
|
-
The retained original source owner inside the wrapper separately validates and
|
|
82
|
-
executes the accepted Ruby snapshot and its package closure.
|
|
83
|
-
|
|
84
|
-
Release is one compact JSON record containing the original ticket/assignment/
|
|
85
|
-
attempt/generation/commit plus `prepared_input`. That closed object pins the
|
|
86
|
-
original registration generation/commit, definition and original binding digest,
|
|
87
|
-
prepared-work reference, original worker entry pair and exact journal bundle reference/length/SHA256. The
|
|
88
|
-
authority verifies the original bundle before durable issue; subsequent prepared
|
|
89
|
-
fetch authenticates the accepted release pin before returning body bytes. The
|
|
90
|
-
gate validates the closed ten-field pin, including the original workspace exclusion,
|
|
91
|
-
digests and unchanged bounds before exec. The exclusion has five closed fields:
|
|
92
|
-
key, authority UID/GID, readonly root resource and original cwd resource. Each
|
|
93
|
-
resource has the existing eight identity fields. The gate checks integer/path/
|
|
94
|
-
filesystem bounds and exact mapping/view/key association; the retained Ruby owner
|
|
95
|
-
verifies the canonical cwd digest and held filesystem identity before acquiring
|
|
96
|
-
the lifetime reader. This metadata does not itself grant filesystem access.
|
|
97
|
-
The complete encoded release is bounded to16KiB before durable issue and at the gate. It
|
|
98
|
-
does not receive inline work or propagate an input FD to providers. The worker
|
|
99
|
-
uses the existing authenticated original prepared fetch after release.
|
|
100
|
-
|
|
101
|
-
An ordinary Docker kernel with Yama=0 can verify compilation and fail-closed
|
|
102
|
-
policy rejection. It cannot demonstrate protected launch acceptance. A real
|
|
103
|
-
isolated Linux deployment enforcing the complete policy is required for that
|
|
104
|
-
positive proof; installing this gem does not change host sysctls or install OS
|
|
105
|
-
service/account policy. Downstream gad.8 provisions those requirements and
|
|
106
|
-
consumes this shipped build mechanism.
|
|
107
|
-
|
|
108
|
-
## Fixed digest initialization and acceptance limits
|
|
109
|
-
|
|
110
|
-
Before any OpenSSL API call the gate selects `OPENSSL_INIT_NO_LOAD_CONFIG`.
|
|
111
|
-
It creates a private library context, fixes its provider search path to `/dev/null`
|
|
112
|
-
(a non-directory), explicitly loads only `default`, and fetches `SHA256` with
|
|
113
|
-
`provider=default`. Failure refuses execution; no config/engine/module fallback
|
|
114
|
-
is provided. [Initialization semantics](https://docs.openssl.org/3.0/man3/OPENSSL_init_crypto/),
|
|
115
|
-
[provider search path](https://docs.openssl.org/3.0/man3/OSSL_PROVIDER/), and
|
|
116
|
-
[fixed provider property](https://docs.openssl.org/3.0/man7/OSSL_PROVIDER-default/) and
|
|
117
|
-
[libcrypto built-in default provider](https://docs.openssl.org/3.0/man7/crypto/#default-provider)
|
|
118
|
-
are the maintained API basis. The system libcrypto/loader remain in the accepted
|
|
119
|
-
root-controlled OS artifact closure; this does not attest an arbitrary library build.
|
|
120
|
-
|
|
121
|
-
This source checkpoint does not compile or execute native code. Real ELF,
|
|
122
|
-
OpenSSL linkage/provider availability, memfd seals, retained FD execution and
|
|
123
|
-
Python script/stdlib behavior require installed Linux acceptance. The trusted
|
|
124
|
-
provisioned OS/Python premise remains; no separate Python runtime tree is introduced.
|
|
1
|
+
# Native worker bootstrap (retired from the file-based flow)
|
|
2
|
+
|
|
3
|
+
The `ace-runtime` gem still ships `native/worker_gate.c` and
|
|
4
|
+
`native/build-worker-gate` from the replaced protected model. They are not part
|
|
5
|
+
of the current file-based flow: no command, task, assignment, or local
|
|
6
|
+
documentation requires the native gate, its authority file, or its admission
|
|
7
|
+
checks.
|
|
8
|
+
|
|
9
|
+
Do not treat this directory as an owner of execution state. The supported
|
|
10
|
+
`ace-runtime` surface is the terminal intent contract described in the
|
|
11
|
+
[README](../README.md) and [usage](../docs/usage.md). Removal or redesign of the
|
|
12
|
+
native bootstrap is tracked with the L1/L2 cleanup; this note keeps the
|
|
13
|
+
historical source discoverable without presenting it as a requirement.
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ace-runtime
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michal Czyz
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-10-
|
|
10
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: fiddle
|