glove/docs/session-policy.md
0xkite 6e47c25fdc
fix(container): admit inherited socket probes
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.
2026-09-01 11:38:09 -06:00

542 lines
30 KiB
Markdown

# Gloved session policy
`gloved` validates remote identifier-only plans against operator-owned local
policy. Remote requests cannot supply executable paths, host paths, environment
variables, or raw secrets.
## Capability activation
| Configuration | Advertised session methods |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Base service | authenticated receipt paging and acknowledgement |
| `--session-policy` | `validate_plan` |
| plus `--session-store` | `create_session`, `session_status` |
| Linux plus `--materialization-root` | `start_session`, `attach`, `write_stdin`, `resize`, `signal`, `detach`, `stop_session`, `cleanup_session` |
| validated `remote_backend` | construction diagnostic only; no lifecycle methods |
| plus `--library-bundle-root` | digest-bound read-only bundle projection |
| `--path-exposure-policy` plus `--path-exposure-journal` | local exposure administration and redacted catalog |
Capabilities are derived from successfully constructed and operational
components, not pointer presence. macOS does not advertise managed-session
resource enforcement. The construction-only remote runtime reports
`lifecycle_operational() == false`, adapter schema `0`, no managed runtime IDs,
and no resource or verified-receipt capability. Its local lifecycle methods are
unavailable through the protocol even though validated SSH artifacts and a
runtime object exist.
`refinement_evaluation_protocol_schema_version` is `1` only when the Linux
managed runtime and protected library-bundle store are both constructed.
macOS and a Linux runtime without that store report `0`.
`refinement-eval-v1` plans require a strict `refinement` binding and exactly two
library projections: the fixture and the selected base or candidate skill. The
binding commits the fixture, base, and candidate projection IDs, SHA-256
digests, and destination aliases, the selected variant, and a matched-context
digest. Path grants are unavailable in V1. Missing bindings or an attempt to
fall back to the resource receipt V1 are rejected.
The fixture projection is canonical JSON using
`glove.refinement-eval-fixture-v1`. Unknown fields, duplicate/noncanonical
encoding, invalid UTF-8, unsafe bounds, or a projection/session/variant mismatch
fail before child release. The fixture binds:
- evaluation run, matched pair, session, and base/candidate variant;
- proposal, base projection, candidate projection, fixture, and dataset
digests;
- fixture ID, dataset reference, deterministic seed, model identity, harness,
and optional module identity/selectors;
- the selected skill projection;
- expected termination and exit code, optional maximum wall latency, and
canonical sorted required/forbidden UTF-8 transcript literals.
Remote configuration is mutually exclusive with local materialization and
Apple Container configuration. Startup prepares only mode-0600 SSH config and
known-host artifacts beneath the verified mode-0700 runtime directory. It does
not contact the configured host. Remote policy acceptance therefore remains
separate from launch authority.
## Cloudflare OS development template
`tests/fixtures/cloudflare-os-session-policy.json` is the checked-in,
owner-authored policy template for `runtime_id: "cloudflare-os"` and
`runtime_template_id: "cloudflare-os-dev"`. It uses
`remote_linux_container`; it is not a native Agent Skills adapter and does not
extend the closed native-adapter table. The plan selects the template ID and
its `adapter_command_digest`. The executable path, argument, environment, and
immutable closure remain operator-local policy fields.
The template commits `/opt/glove/runtime/cloudflare-os/node-v25/bin/node`, the
single fixed absolute argument
`/opt/glove/runtime/cloudflare-os/closure/scripts/run-local.mjs`, an empty
environment, and the read-only `/opt/glove/runtime/cloudflare-os/closure`. The
script is therefore selected from the immutable closure even when the writable
workspace contains a shadow `scripts/run-local.mjs`. The checked digest is valid
only for those exact fields. An operator changing any path or launch field must
recalculate it with the existing canonical `runtime_launch_template_digest`
implementation and revalidate the owner-only policy; a plan cannot override any
launch field. Before use, the owner must provision or replace the fixed
workspace and `/opt/glove` paths, protect the policy as mode `0600`, and
revalidate it. Provisioning the pinned Node, pnpm, workerd, or source closure is
outside this artifact and must not be performed by a session plan.
The only workspace mode is a quota-bounded 4 GiB `ephemeral_write` copy at
`/workspace`, removed after the session. Read binds, `direct_write`, and
`retained_write` are not authorized. `cfos-small` fixes 120 seconds of CPU,
2 GiB memory, 256 PIDs, five minutes wall time, 6 GiB disk, and 16 MiB terminal
output. `max_plan_ttl_ms` preserves the required one-second launch headroom.
No library projection or secret handle is authorized.
The named `cloudflare-os-loopback` egress policy contains exactly
`127.0.0.1:8787` with `allow_private: true`. No wildcard, non-loopback address,
or extra inspector port is present. This policy records the intended target;
network-namespace routing to the authenticated per-run proxy remains part of a
future operational remote lifecycle.
Loading or accepting this template does not make remote lifecycle operational.
The remote runtime still reports `lifecycle_operational() == false`, so
`start_session` and every other lifecycle method remain unadvertised and return
`method_not_found`. Policy and plan acceptance are construction-only until a
separate implementation supplies and tests the remote enforcement lifecycle.
Glove derives a plan-context digest from runtime, adapter, backend, network/tool
policy IDs, secrets, six resource limits, policy revision, and both skill
projection commitments. It excludes expiry and the selected variant/projection.
The normalized fixture context excludes only session, variant, and selected
skill projection. Their combined matched-context digest therefore permits only
the intended variant/session/expiry/skill-projection deltas across a matched
pair.
The evaluator consumes the complete online PTY stream, independently of the
bounded circular attach transcript. It never parses model output as a result
frame. The synthesized metrics are `passed`, `latency_us`,
`failed_assertions`, `required_literals`, and `forbidden_literals`. Token counts
and tool/security/egress event assertions are intentionally absent because the
current managed-session architecture does not expose those as trusted
session-bound observations.
The distinct authenticated refinement receipt carries the exact projections,
variant, matched context, synthesized outcome and commitment, complete raw-PTY
digest, resource receipt, and evaluator completeness. It shares the durable
journal sequence with resource receipts but has separate receipt/envelope HMAC
domains and a distinct registry transition. Torn-tail recovery, exact paging,
replay rejection, and terminal reconciliation apply to both receipt kinds.
`max_plan_ttl_ms` must exceed every resource profile's `wall_time_ms` by at
least one second. That headroom is reserved for approval and launch; Glove and
Sage reject a policy that cannot launch its own resource profiles. The guided
generator uses a ten-minute plan lifetime. It emits a two-minute `small`
profile for probes and short turns plus an explicit five-minute `interactive`
profile for model-backed turns that may spend most of their lifetime waiting on
audited egress.
`agent_runtime_adapter_schema_version` is independent of the raw process/PTY
lifecycle. It is `1` only for an active Linux managed-session runtime: Glove
creates a fresh private harness home, expands verified Sage `skill` bundles
into that adapter's native Agent Skills directory, and commits the derived
projection digest, home mount, and managed environment to the launch profile.
The authenticated terminal receipt is bound to that profile digest. Sage
requires version `1` before remote launch.
Glove images do not bundle agent harnesses or their credentials. A runtime
template may instead name bounded `launch.read_only_paths` for immutable files
belonging to an operator-installed harness (such as its package directory).
Glove mounts those paths read-only; Sage selects only the template ID and its
digest, never an executable or a host path. Runtime paths cannot be `/`,
relative, duplicate, or overlap a pinned executable.
Credential policies name exact owner-protected source files, but controller
plans contain only opaque handles. Linux launch imports each source into a
content-versioned Glove-owned lease, serializes use with an exclusive lock,
and mounts the lease writable at the adapter's exact path in its private home.
This supports vendor token rotation without granting the sandbox access to the
operator's source file or broader harness home.
For a supported native-skill runtime on Linux, an operator can replace a
pinned `executable_path` with a matching `runtime_discovery` value and an empty
executable path. Supported values and their Glove-owned private locations are:
| Runtime | Executable | Skill directory | Managed environment |
| ------------- | ---------- | ------------------------------------- | ------------------------------------- |
| `codex` | `codex` | `/home/agent/.codex/skills` | `CODEX_HOME=/home/agent/.codex` |
| `claude-code` | `claude` | `/home/agent/.claude/skills` | — |
| `pi` | `pi` | `/home/agent/.pi/agent/skills` | — |
| `copilot` | `copilot` | `/home/agent/.copilot/skills` | `COPILOT_HOME=/home/agent/.copilot` |
| `opencode` | `opencode` | `/home/agent/.config/opencode/skills` | `XDG_CONFIG_HOME=/home/agent/.config` |
Discovery requires a non-empty ordered `executable_search_paths` list. Each
directory is an explicit, digest-bound local policy value; Glove rejects any
directory or ancestor that is not owned by the service account or root, or is
writable by another principal. Glove never searches its inherited `PATH`. It
canonicalizes and identity-pins the selected file before execution. The adapter
ID, explicit search paths, arguments, environment, and read-only dependencies
are all covered by the local adapter digest. A Sage plan cannot select a
directory, binary, arguments, adapter, private-home path, or managed
environment. Discovery is rejected when it does not match the template's
runtime ID and fails closed when no executable is installed. Linux-production
templates may enter the authenticated managed-session lifecycle. macOS
templates use the same local discovery boundary only for SBPL-contained local
execution and remain experimental: Glove does not advertise resource
enforcement or remote managed-session capability there.
Package-manager bin directories are often group-writable. Explicit setup
discovery may inspect one, but it is not launch authority. `policy stage`
content-addresses and copies a supported unsafe single-root package/interpreter
closure into a dedicated protected runtime directory before the normal launch
resolver admits it. The protected snapshot—not the Sage plan or original
package-manager tree—remains the authority for the selected harness.
The owner-local workflow is programmatic:
```sh
glove policy detect --search-path /absolute/directory --json
glove policy stage --runtime codex --source /absolute/codex \
--directory /absolute/protected/codex --dry-run
glove policy stage --runtime codex --source /absolute/codex \
--directory /absolute/protected/codex --yes
glove policy generate --runtime codex \
--executable /absolute/canonical/codex --template-id codex-safe
glove policy explain --file /absolute/session-policy.json --json
glove policy validate --file /absolute/session-policy.json
```
`policy generate` emits one strict `runtime_templates[]` JSON entry after
canonicalizing either a pinned `--executable` or explicit `--search-path`
roots, resolving the adapter executable, and calculating its digest.
Arguments, canonical environment, read-only dependency paths, allowed aliases,
and projection destinations are repeatable explicit flags. `policy stage`
never reads or copies a harness home and never overwrites an existing entry
point. Its report includes the content digest when package/interpreter closure
copying was required. Pinned mode is the portable default for a Linux service in an
unprivileged user namespace: unmapped host-root ancestors retain an ambiguous
overflow UID and discovery continues to reject them rather than assuming they
were root-owned.
`direct_write` remains a legacy parse-only value and is rejected at launch.
New write-capable plans use isolated `retained_write` materializations; applying
their changes to the source is a separate, not-yet-enabled operation.
On Linux, retained copies use a hard-sized ext4 image attached through an
autoclear loop device so the staged filesystem survives daemon and host
restarts. Ephemeral copies and session scratch remain quota-sized tmpfs mounts.
Retained images require a root-owned, non-writable `/usr/sbin/mkfs.ext4` or
`/sbin/mkfs.ext4`, loop-control access, and mount capability.
## Protected files
| Object | Required properties | Bound |
| --------------------------------------- | ------------------------------------------------------------------------------ | ------------------------- |
| Runtime and materialization directories | absolute, current-user owned, mode `0700` | dedicated paths |
| Policy | regular file, current-user owned, mode `0600`, one hard link, no final symlink | 1 MiB |
| Session store | parent mode `0700`; regular file mode `0600`; one hard link; exclusive writer | 64 MiB and 10,000 records |
| Bundle root | directory mode `0700`, identity-pinned | digest filenames only |
| Bundle | regular file mode `0600`, one hard link, no symlink | 16 MiB |
Policy and bundle reads use one descriptor and reject metadata or identity
changes during validation. Policy is frozen for the process lifetime; replace
it atomically and restart `gloved` to change it.
## Dynamic path exposures
Exposure policy defines owner-approved roots, exact mode/quota tuples, maximum
TTL, and runtime templates. The paired journal records local descendant leases.
Both files require owner-only placement; the journal is bounded, hash-chained,
single-writer, and replayed strictly.
On Linux, descendants resolve with `openat2` using `RESOLVE_BENEATH`,
`RESOLVE_NO_SYMLINKS`, `RESOLVE_NO_MAGICLINKS`, and `RESOLVE_NO_XDEV`.
Other platforms use component-wise no-follow descriptor traversal. A catalog
entry exposes only its ID, generation, scope digest, label, allowed modes,
expiry, runtimes, and state. Paths, root IDs, and source identities remain
local. Replacing a live generation requires revocation or expiry.
`glove init <project>` is the owner-local enrollment client. It canonicalizes
the project path and requests one exposure beneath a root created by
`glove setup --path-root`; remote session requests cannot invoke enrollment or
supply a host path.
## Policy schema
The decoder rejects unknown fields. This example defines one runtime, one
ephemeral workspace grant, one library destination, and one mandatory resource
profile:
```json
{
"schema_version": 1,
"revision": 7,
"max_plan_ttl_ms": 120000,
"runtime_templates": [
{
"runtime_template_id": "codex-safe",
"runtime_id": "codex",
"adapter_command_digest": "05a49649e7973f6f8d6b119c9d525472517e6021fb38f8b191e0b40c8c4741d0",
"sandbox_backend": "linux_production",
"allowed_path_aliases": ["workspace"],
"allowed_projection_destinations": ["libraries"],
"launch": {
"executable_path": "/usr/bin/true",
"arguments": ["--version"],
"environment": ["PATH=/usr/bin:/bin", "TERM=xterm-256color"],
"read_only_paths": ["/opt/operator-harness/codex-runtime"]
}
}
],
"path_aliases": [
{
"alias": "workspace",
"host_path": "/srv/work/sage-protocol",
"target_path": "/workspace",
"max_ttl_secs": 120,
"access": [
{
"access": "ephemeral_write",
"materialization": "copy",
"create_policy": "empty_directory",
"cleanup_policy": "remove",
"max_bytes": 2097152
}
]
}
],
"library_projection_destinations": [
{
"alias": "libraries",
"target_path": "/opt/sage/library-bundles"
}
],
"resource_profiles": [
{
"profile_id": "small",
"cpu_time_ms": 1000,
"memory_bytes": 67108864,
"pids": 16,
"wall_time_ms": 2000,
"disk_bytes": 2097152,
"terminal_output_bytes": 1048576
}
],
"egress_policy_ids": ["no-network"],
"tool_policy_ids": ["sage-readonly"],
"secret_handles": ["codex-token"]
}
```
A submitted plan must match:
- policy revision, expiry window, runtime and adapter digest;
- sandbox backend and all six numeric resource limits;
- egress, tool-policy, and secret identifiers;
- path aliases, access modes, materialization, cleanup, TTL, and byte quotas;
- library digests and configured destination aliases.
Retained changes are staged and inspected separately from host mutation. The
apply authorization schema, root-controlled Ed25519 trust-policy loader,
durable single-use reservation journal, and Linux whole-entry atomic-exchange
primitive are present. The primitive rejects stale baselines, copies without
following links, recursively syncs the candidate, uses
`renameat2(RENAME_EXCHANGE)`, and retains the old source for recovery. No
production signature backend or privileged signing helper is wired. Receipt
construction, reconstructable durable terminal finalization, prepared-candidate
discard, committed-state recovery, and idempotent baseline cleanup are
implemented locally. Grant IDs, authorization digests, and manifest digests are
each globally single-use. Ambiguous states remain consumed and non-terminal. These
operations are not yet connected to the control server, receipt audit stream,
or startup recovery sweep. Glove therefore advertises apply schema version `0`
and rejects apply operations.
Apply supports only service-owned, single-link, non-sparse regular files and
directories without ACLs, extended attributes, symlinks, special files, or
special mode bits. Contents and child modes come from the frozen stage; the
source-root mode is retained; ownership is normalized to the Glove service
identity; timestamps are intentionally regenerated. A production deployment
must also prevent Sage from mutating the source parent during the final
identity-check/exchange interval. Same-UID access to that parent is not a
sufficient boundary.
The pre-reservation and execution checks also require the full logical staged
tree plus a 64 MiB reserve to fit in the source filesystem's currently
available blocks. Copy bounds and filesystem errors still fail closed if
concurrent disk use consumes that headroom.
Arrays must be canonical and unique. Host paths and sandbox targets remain local
to the policy.
## Durable identity
Session creation stores the canonical plan and two digests:
- `controller_plan_digest`: Sage's 64-hex BLAKE3 correlation digest;
- `plan_content_digest`: Glove's SHA-256 digest of the exact canonical JSON.
A byte-identical retry with the same idempotency key returns the original
record. Reusing a session identity or key with different content fails closed.
Recovery rejects incomplete, corrupt, reordered, duplicate, or externally
resized records.
The lifecycle is:
```text
created → preparing → starting → running → stopping → exited
└──────────────────────────→ failed
```
Transitions are append-only and idempotent. Start authorization is short-lived
and bound to the session and both plan digests.
## Filesystem and launch binding
Path aliases are opened component-by-component without following links.
Restricted roots, overlapping aliases, non-canonical targets, and unsupported
file types are rejected. Resolved grants retain descriptor identity rather than
exposing a public raw host path.
Bundle requests contain a lowercase SHA-256 digest and destination alias. Glove
opens the digest-named file relative to the pinned root, rehashes it, and mounts
it read-only at the configured target. The launch binding and authenticated
terminal receipt commit each projection's identifier, destination, target, and
digest.
Runtime templates bind either an absolute executable or a constrained built-in
native-adapter discovery surface, plus ordered arguments and canonical environment, to
`adapter_command_digest`. Sage and Glove derive that digest independently.
Linux preparation then binds the resolved launch to cgroup identity,
quota-backed filesystems, the six limits, and resolved projections.
For `runtime_id: "codex"`, Glove never uses an image-bundled client or an
operator's existing Codex home. It creates `/home/agent`, injects
`HOME=/home/agent` and `CODEX_HOME=/home/agent/.codex`, and materializes only
the exact verified `skill` entries requested by the Sage plan. The operator
must still locally configure either the executable or Codex discovery paths,
plus any immutable dependencies, through the launch template. Network
credentials and egress remain separate policy features and are not exposed by
this adapter.
On macOS, `container_macos_codex_discovery` is a local live probe. When Codex
is present on the `gloved` service PATH, it resolves the canonical executable
through the policy and runs `codex --version` under `sandbox-exec`; otherwise
CTest marks the probe skipped. `scripts/testing/glove_local_harness.sh` runs
that lane alongside the filesystem and environment SBPL probes. Linux keeps
the privileged Docker workflow lane for namespace, cgroup, receipt, cleanup,
and no-network assertions.
## Guest observation channels and Sage guest proposals
The guest service channel is schema-generic: Glove core enforces structural
invariants (identifier charset, digest hex, TTL/skew arithmetic, capacity,
idempotency) and delegates body semantics to the host-registered admission
table. Schemas are registered by the host at registry construction, never
compiled into core. Current admission and enqueue fail closed for an
unregistered schema. Recovery may retain an authentic historical record after
its schema retires only under the metadata-only quarantine rule below.
The Sage guest adapter registers only the bounded observation schema required
for current registry admission. Dormant proposal and mutation schemas remain
unregistered until an authenticated activation path exists, and current enqueue
rejects every unregistered schema. Only authentic records already present in
durable history may recover after schema retirement; they become non-actionable
quarantine metadata and cannot be enqueued again or launched.
Linux local-service forwarding is enabled when operator configuration has a
runtime-allowlisted endpoint. Configuration contains only a canonical sorted
alias list, normalized absolute host Unix-socket paths, canonical sorted runtime
IDs, and an optional opaque guest-channel adapter/schema binding; guest targets,
TCP, UIDs, tokens, credentials, and payload semantics are not configurable.
Glove core does not interpret that binding. The explicit Sage adapter resolves
only its exact binding and runtime set; an unsupported binding fails startup,
while an unbound arbitrary service—including one allowed for `pi`—remains a
generic proxy and advertises no Sage channel capability. Codex-only and
OpenCode-only endpoint sets likewise advertise schema version `0`.
When the adapter transport selector is absent, each admitted session receives
the compatibility read-only directory at `/run/glove-services/local` and only
`GLOVE_LOCAL_SERVICE_DIR=/run/glove-services/local`. Alias sockets are `0600`;
their owner-`0700` directory and the configured upstream parent remain
descriptor-pinned. Endpoint pathname identity is checked before and after
connect, but portable pathname `AF_UNIX` does not exclude same-UID ABA
replacement. The operator and other processes under the service UID are trusted
endpoint authority. Linux `SO_PEERCRED` must match the configured UID at both
guest and upstream connections; this detects drift and mismatched peers, not
application-process identity.
For the owner-selected `inherited-stream-v1` adapter transport, Glove instead
creates one nonblocking close-on-exec `AF_UNIX` stream socketpair for the exact
adapter-bound alias. The child receives the guest endpoint at descriptor 3,
`GLOVE_LOCAL_SERVICE_FDS_V1={"<alias>":3}`, and the adapter-owned alias selector.
No local-service pathname, legacy endpoint/root/owner tuple, secret, network
route, or other configured service descriptor is projected. The clone/exec lane
loads a filter bound to the exact contiguous child-FD range, installs only those
committed descriptors after setup closes its internal FDs, and closes every
undeclared descriptor. Offline `socket`, `connect`, and mutation syscalls remain
denied; only `getsockname` and `getsockopt(SOL_SOCKET, SO_TYPE)` may inspect the
committed streams for runtime adoption. The supervisor retains the peer for
sequential bounded exchanges; framing, queued input at the request boundary or
immediately around response release, upstream identity drift, timeout,
cancellation, audit-gate, or transport failure permanently closes the stream.
Mixed or operator-supplied reserved environment authority is rejected before
launch. The managed-launch digest binds the transport, alias/FD map, both socket
endpoint identities, and the existing upstream manifest. Capability version `1`
requires this exact inherited composition; the compatibility pathname transport
continues to advertise `0`.
Before any path materialization, Glove compares each resolved grant descriptor
with the descriptor-pinned endpoint parent and every actual ancestor descriptor. A grant
for the socket, its parent, or an ancestor that would expose it is rejected;
lexical aliases do not bypass this identity check. The nested user-namespace
credential lane remains a Linux privileged environment gate.
One opaque request and response, each at most 16 KiB, share one absolute
deadline. Before release, Glove durably records non-success
`delivery_pending` and rechecks that no next request arrived during durability
work. It records `delivered` only after a successful guest send and an immediate
post-send queue check, and best-effort `delivery_failed` after failure. Records
contain only the session ID, alias, phase, status, and latency. Audit appends
check the same deadline before and after synchronous durability work. A
kernel-blocked `fsync(2)` cannot be preempted cooperatively, so this is not a hard
wall-clock bound; if it completes after the deadline, the response remains
suppressed and the conservative pending record remains. Bytes arriving after
the final post-send peek are serialized as a possible next request; a causal
no-pipelining claim would require a future credit/ack protocol. Apple advertises
no G6 capability.
## Receipts and recovery
Terminal resource receipts are HMAC-SHA-256 authenticated and chained to Sage's
trusted anchor. The producer reserves journal capacity before launch and
durably appends the terminal envelope before success. Delivery is paged and
acknowledgement must match the exact head; Sage archives before acknowledging.
Recovery identifies processes by boot ID, PID start time, and cgroup identity,
not PID alone. Identity mismatch has no signaling side effect. Orphaned
materializations are removed only after quota and descriptor checks. Retained
images are remounted and frozen before cleanup; volatile materializations lost
across a host restart are removed as empty mountpoints.
Reserved apply transactions can reopen the exact journaled exposure parent by
descriptor after lease revocation or expiry. This recovery-only lookup requires
the exposure ID, generation, scope digest, and original source-identity digest;
the local exposure journal also binds the parent directory identity, so a
replacement at the same path is rejected. The lookup exposes neither a raw path
nor launch authority.
Recovery classifies only four descriptor-proven states: reserved, prepared,
committed, or ambiguous. Reserved and safely discarded prepared transactions
receive durable failed receipts. Committed exchanges receive durable applied
receipts before baseline cleanup. The journal stores the stable failure code
and all receipt bindings, so either terminal receipt can be reconstructed after
restart. Failed terminal receipts require no surviving stage or exposure
locator. Applied terminals resolve those objects only to retry baseline
cleanup. The bounded startup reconciler reports redacted per-record issue
codes. Ambiguous transactions are never retried or terminalized automatically.
## Remaining boundary
Glove's native adapters expand only exact `skill` bundle entries into a
private-home `SKILL.md` layout. `behavior`, `prompt`, `template`, and
`workflow` are admitted only as known non-skill entries: they remain in the
verified raw bundle and make that raw bundle a required launch input rather
than being interpreted as skills. Apple Container rejects any projection
that contains a known non-skill entry because its launch path cannot preserve
the required raw bundle. Unknown kinds fail closed. Harnesses do not inherit
Codex's layout, and `prompt_ref` remains rejected. Live direct host writes
are outside v2.
Applying a retained stage remains unavailable until Glove verifies an
independently signed, single-use local authorization.