Commit graph

3 commits

Author SHA1 Message Date
0xkite
4a487dafdb
feat(control): generalize the guest specialized-channel layer
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>
2026-08-29 16:04:27 -06:00
velinussage
2fa18a2e97 Connect Sage guest observations to managed sessions 2026-08-29 01:32:19 -04:00
velinussage
4385e1b5ee Add per-session guest observation socket 2026-08-29 01:04:55 -04:00