Keep gloved in a systemd-owned supervisor leaf while creating sibling session cgroups under the empty delegated parent. Under mapped-root supervisors, attach blocked children before releasing their existing start gate instead of combining nested UID mapping with CLONE_INTO_CGROUP.
Permit Node/libuv read-only type inspection only on committed child
descriptors while offline socket and connect remain denied.
Recheck queued input after audit and around response release. Late
pipelined frames now poison the channel instead of crossing the gate.
Combine maintainer/wallet-status-and-apple-repairs (18e75af8) with
maintainer/guest-channel-layer (00d7c473) into a single validated
integration branch for Ghost deployment testing.
Merge resolution decisions:
- Apple container runtime (the known conflict zone) auto-merged
textually and was verified semantically against both parents:
the maintainer hard-disable survives (lifecycle_operational()
returns false unconditionally, apple_container_session_runtime.hpp),
the sampler/finalizer jthread lifecycle seam and the finalizer
catch-all well-formed terminal state (a96ab1e) are intact, and
null_terminated=false strict_json_options is preserved. Velinus's
observation-intent integration lands adapted on the repaired base:
prepare_observation_service is only reachable from start(), which
denies before side effects when the lifecycle is disabled, so the
observation service never constructs on the disabled Apple path.
The gated test from PR #25 (every control method refuses before
side effect; record counts unchanged) survives.
- GLOVE_GUEST_OBSERVATION_* env names kept; observation socket/token
volumes still mount only when observation_service->active().
- guest_channel layer, host-registered schema admission
(channel_descriptor/channel_host), SO_PEERCRED/LOCAL_PEERCRED, LRU
replay cache, bounded retry, fail-closed recovery, and
channel_identifier_grammar.hpp land as-is; session_registry files
did not conflict with PR #24's side.
- Wallet status files: PR #25's delta did not touch them, so the
harness-neutral renames (wallet_status_projection_channel) stand
unchanged.
Validation (umask 0022): dev/asan/tsan presets build clean and
CTest 80/80 each (control_apple_container_session_runtime
env-gated/skipped as expected); clang-format (llvm@22) applied to
changed sources; git diff --check clean. No push.
Add the fail-closed recovery regression: reopening a store holding
durable observation intents must fail without the registered schema
table — neither a null channel host nor an empty one (no registered
schemas) can admit the frozen intents, so recovery is refused at the
session_registry_impl.hpp channel guard.
Cut speculative generality with no consumer: the channel_descriptor
on_exchange callback and its exchange_hook plumbing in
enqueue_observation_intent return when the wallet-bridge unification
lands; likewise channel_host::catalog() and channel_catalog_entry had
zero callers and no authorized RPC.
Consolidate the identical 128-byte bounded charset identifier and
64-hex digest grammar (previously duplicated in guest_channel.cpp,
session_registry_impl.hpp, and observation_intent_unix_server.cpp)
into src/control/channel_identifier_grammar.hpp without changing
semantics, and document the cached-replay vs registry-replay response
divergence plus that channel_id is registration metadata only
(registry admission matches by schema_id).
Generalizes the PR #23 guest observation plumbing into a schema-agnostic
specialized-channel layer while keeping all harness policy out of core.
Generalized admission (F2/HIGH):
- New channel_descriptor/channel_host interface (include/glove/control/
guest_channel.hpp): hosts register {channel_id, schema_id,
body_validator, bounds{max_items, max_body_bytes, max_ttl_ms,
max_skew_ms}, on_exchange} at registry construction; core keeps only
structural invariants (identifier charset, digest hex, TTL/skew
arithmetic, capacity, idempotency) and delegates body semantics to
registered validators.
- The closed two-schema check in session_registry internals is removed:
glove_observation_schema, glove_sxxx_proposal_* constants and the
fixed SXXX digest move out of session_registry_impl.hpp. The Sage
schemas are now registered by the harness adapter
(apple_detail::sage_guest_channel_host) and gloved_main wires the host
into the registry; schema strings appear only in harness-adapter and
test-fixture registration examples.
- enqueue_observation_intent binds intents to the session's PARSED
runtime_id; the hardcoded sage_guest_runtime equality checks are
removed from session_registry.cpp, session_registry_recovery.cpp and
valid_record_shape. Replay of a now-unregistered schema fails closed
(open_or_create rejects stores carrying unadmitted intents), and
enqueue without an admission table is rejected. The observation
socket server takes runtime_id from configuration instead of
hardcoding "sage-guest". The observation body hash domain becomes the
harness-neutral "glove.observation-body.v1" (pre-merge delta; no
on-disk compatibility is preserved).
Recovery fix (F1/HIGH):
- same_session_snapshot now neutralizes state, running_at_ms and
policy_revision before comparing frozen intent/disposition snapshots
against the bound session record, so lifecycle progression and policy
revision advances never read as a binding crossing; every other
frozen-field difference still does. Exposed for tests. Regression
tests cover running->stopping and stopping->exited transitions plus a
late post-exit disposition end to end (destroy/reopen), and a
snapshot-identity unit test pins the neutralization (and proves
non-neutralized fields still cross). Note: an empirical probe showed
the previously-claimed failure does not reproduce under ordered
journal replay because intent/disposition records freeze the session
record they were appended from; the neutralization is applied as the
defensive fix the memo requires and is now pinned by tests.
Hardening:
- F3: peer-uid verification (SO_PEERCRED / LOCAL_PEERCRED) on observation
socket accepts, configurable expected uid, rejecting unexpected peers
before any frame is read.
- F4: the replay cache is a bounded LRU (configurable capacity), so the
old fixed 1024 cap can no longer permanently kill the channel; the
durable registry remains the authoritative idempotency layer.
- F5: transient wait/accept errors (EINTR/EAGAIN/ECONNABORTED/EMFILE/
ENFILE/ENOBUFS/ENOMEM) are retried inside serve_one_for with a bounded
backoff ladder; only persistent or fatal errors surface. The Apple
worker loop absorbs persistent failures up to a consecutive-failure
bound and emits an audit event (new audit action "observation") on
repeated failures instead of dying on the first hiccup.
- F6: env/mount names generalized to GLOVE_GUEST_OBSERVATION_SOCKET /
GLOVE_GUEST_OBSERVATION_TOKEN_FILE and the in-container token path to
/home/agent/.glove-observation-token (Apple runtime, docs).
Apple boundary: the soft lifecycle_operational() definition is unchanged;
the lifecycle-disabled lane now additionally asserts start_session/
stop_session deny with method_not_found and leave the registry untouched
(denial before side effects; the observation service only constructs
inside a lifecycle-gated start).
Docs: architecture.md and session-policy.md describe the guest
specialized-channel admission model and the peer-uid/env naming.
Validation (umask 0022):
- cmake --preset dev/asan/tsan + build + ctest: 100% (76/76) in each
configuration; asan/tsan are warnings-as-errors builds.
- git diff --check clean; changed files clang-formatted with Homebrew
llvm@22 against the repo .clang-format.
- No fuzz target exists for the queue/registry (fuzz/ covers plans,
bundles, manifests, MCP codec, jsonpath only), so no fuzz corpus run.
- control_remote_validation_executor is a pre-existing load-sensitive
sleep-race test; it flakes identically on the unmodified pr23-head
baseline under CPU load (verified 1/6 both trees) and always passes
isolated.
Files: 23 changed (+~1500/-~360), including new guest_channel.{hpp,cpp}.
Signed-off-by: 0xkite <me@hack4kites.dev>
GLM complementary review remediation on the wallet stack:
- The Pi status adapter content string now reflects the structured
connected flag ("Wallet is not connected.") instead of an
unconditional "Wallet status is available." that contradicted
connected:false; bound by a new REQUIRE on the disconnected path.
- The wallet_status_json fuzzer clock now advances its monotonic
component from fuzz input, so fuzzed frames traverse the deeper
revalidation ladder and deadline paths instead of a single fixed-
authority success path.
- docs/architecture.md: documented the one-strike authority teardown
contract (no retry/resync by the bridge) and the ABA generation-
reuse defense living in authority contracts, gated on adversarial
authority tests before any live composition.
Validation (umask 0022): dev CTest 79/79, ASan/UBSan 79/79,
TSan 79/79; wallet_status_json fuzzer 10,000 units fuzz_exit=0;
clang-format clean; git diff --check clean.
Pi-facing wallet status adapter: private, disabled by default,
status-only projection with strict redaction — no wallet secrets,
no mutation actions, no capability advertisement, unregistered on
the MCP surface. Includes the wallet_pi_status_adapter unit suite
and the argument-order repair for the managed-session invocation.
Layer 5 (final) of the reviewed Glove wallet-status stack.
Validation: dev CTest, ASan/UBSan, TSan, and libFuzzer 10,000 units
per target on this exact stacked tree (see workflow184 layer
records).
Wallet status managed session: exact current session/plan/policy
revalidation, owned trusted-clock zero/rollback/deadline/freshness
checks, and bounded 64-KiB SIGPIPE-safe Unix-socket framing over
the inherited channel. Fail-closed on missing readiness or malformed
observation. No wallet secrets or mutation authority.
Layer 4 of the reviewed Glove wallet-status stack; includes the
wallet_status_managed_session unit suite.
Validation: dev CTest, ASan/UBSan, TSan, and libFuzzer 10,000 units
on the final stacked tree (see workflow184 layer records).
Wallet status bridge: strict typed JSON wire, bounded channel, and
no-mutation status surface. Includes the wallet_status_json fuzzer
with a seed corpus (duplicate method keys, escaped duplicates,
malformed surrogates, nested duplicates, out-of-range requests,
unknown fields, and valid request/response shapes) and the
wallet_status_bridge unit suite.
Layer 3 of the reviewed Glove wallet-status stack; status-only,
disabled by default, and no capability advertisement.
Validation: dev CTest, ASan/UBSan, TSan, and libFuzzer 10,000 units
on the final stacked tree (see workflow184 layer records).
Model and PTY cannot self-report pass or failure. Glove now runs
each refinement-eval-v1 session through a declarative evaluator that
checks bounded fixture assertions (termination cause, exit code, max
latency, required and forbidden transcript literals) against audited
resource events, then binds the evaluated outcome inside the
authenticated receipt digest. Capability advances to version 1 with
the evaluator landed.
Keep agents in a route-less network namespace and pass only private-loopback connections over an inherited Unix descriptor channel. The host relay targets the per-run authenticated proxy, preserving exact allowlists and audit evidence without exposing host networking.
Run the Linux service in a delegated user and mount namespace and preserve precise owner-local launch diagnostics while keeping remote denials generic.
Accept validated v2 session plans without bypassing direct-write consent, and bind explicit native runtimes through the same managed launch and receipt boundary.