WIP: durable opaque context mounts #228

Closed
lost-rob0t wants to merge 16 commits from feature/223-durable-context-mounts into main
lost-rob0t commented 2026-08-26 08:34:53 +00:00 (Migrated from github.com)

Stacked on #225 as Slice 2 of #223.

Contract

  • separates lifetime from visibility: ephemeral | session | persistent vs opaque | prompt;
  • persistent mounts reuse the existing immutable rlm_artifact persistence plane;
  • persistent state stores closed source descriptors + policy, never live context_handles;
  • restart/reopen resolution rehydrates a fresh context handle;
  • trusted adapter callbacks remain process-local and are never serialized;
  • default visibility is opaque; persistence never implies prompt injection;
  • visibility(prompt) is explicit and exposed only through a bounded projection API;
  • scope participates in identity (runtime, session(Id), project(Id));
  • unmount appends a durable tombstone rather than mutating historical state;
  • same source/policy remount is idempotent, changed source creates a new artifact version.

Initial API

context_mount/5
context_mount_resolve/5
context_mount_metadata/4
context_mount_prompt/5
context_mount_unmount/4
context_mount_runtime_reset/0

Focused TDD gate

swipl -q -s test/run_context_mount.pl

The focused tests cover restart rehydration, opaque redaction, bounded prompt projection, versioning/idempotency, unmount tombstones, scope isolation, session lifetime and ephemeral behavior.

test/load_all.pl also loads rlm_context_mount so ordinary CI catches production-module syntax/load failures immediately.

Still WIP: run/fix the focused gate, add full aggregate inventory once stable, and integrate mount metadata into downstream CLI/runtime surfaces without automatically injecting opaque contents.

Stacked on #225 as Slice 2 of #223. ## Contract - separates lifetime from visibility: `ephemeral | session | persistent` vs `opaque | prompt`; - persistent mounts reuse the existing immutable `rlm_artifact` persistence plane; - persistent state stores closed source descriptors + policy, never live `context_handle`s; - restart/reopen resolution rehydrates a fresh context handle; - trusted adapter callbacks remain process-local and are never serialized; - default visibility is `opaque`; persistence never implies prompt injection; - `visibility(prompt)` is explicit and exposed only through a bounded projection API; - scope participates in identity (`runtime`, `session(Id)`, `project(Id)`); - unmount appends a durable tombstone rather than mutating historical state; - same source/policy remount is idempotent, changed source creates a new artifact version. ## Initial API ```prolog context_mount/5 context_mount_resolve/5 context_mount_metadata/4 context_mount_prompt/5 context_mount_unmount/4 context_mount_runtime_reset/0 ``` ## Focused TDD gate ```sh swipl -q -s test/run_context_mount.pl ``` The focused tests cover restart rehydration, opaque redaction, bounded prompt projection, versioning/idempotency, unmount tombstones, scope isolation, session lifetime and ephemeral behavior. `test/load_all.pl` also loads `rlm_context_mount` so ordinary CI catches production-module syntax/load failures immediately. Still WIP: run/fix the focused gate, add full aggregate inventory once stable, and integrate mount metadata into downstream CLI/runtime surfaces without automatically injecting opaque contents.
lost-rob0t commented 2026-08-27 04:41:25 +00:00 (Migrated from github.com)

Preflight adversarial finding before recovery: persistent_mount_cache/3 is keyed only by mount_key(scope,name) + artifact version, not by artifact-store identity. Two simultaneously open artifact stores can each contain the same (scope,name) at version 1 but different source descriptors; resolving the second store may hit the first store's cached live context handle and return the wrong contents. This is cross-store context leakage.

Recovery must bind cache identity to the owning artifact store (or a stable per-store identity) as well as mount key/version, and add a deterministic two-store regression proving same scope/name/version with different sources never shares a live handle. Do not merge/replay #228 as-is.

Preflight adversarial finding before recovery: `persistent_mount_cache/3` is keyed only by `mount_key(scope,name)` + artifact `version`, not by artifact-store identity. Two simultaneously open artifact stores can each contain the same `(scope,name)` at version 1 but different source descriptors; resolving the second store may hit the first store's cached live context handle and return the wrong contents. This is cross-store context leakage. Recovery must bind cache identity to the owning artifact store (or a stable per-store identity) as well as mount key/version, and add a deterministic two-store regression proving same scope/name/version with different sources never shares a live handle. Do not merge/replay #228 as-is.
lost-rob0t commented 2026-08-27 04:42:28 +00:00 (Migrated from github.com)

Additional concurrency preflight findings for the recovery:

  1. ensure_persistent_context/4 does an unlocked cache miss -> registers a fresh handle -> replace_persistent_cache/3 deletes all prior handles. Two concurrent resolves of the same (key,version) can therefore both create handles; the later resolver deletes the earlier resolver's already-returned handle, making the first caller stale immediately. Recovery should use first-live-winner install/double-check semantics: create candidate, then under the mount mutex reuse an existing live winner (delete only the losing candidate) or install the candidate if no winner exists.

  2. persistent_publish_or_reuse/4 performs artifact_latest and artifact_put as separate unsynchronized mount-level operations. Two concurrent identical mounts can both miss/reject reuse and append duplicate versions, violating the advertised same-source/policy idempotence. Serialize the publish-or-reuse decision at the mount layer (artifact backend locking alone cannot make the read+conditional-write atomic).

Add deterministic synchronization tests; no sleep-based races.

Additional concurrency preflight findings for the recovery: 1. `ensure_persistent_context/4` does an unlocked cache miss -> registers a fresh handle -> `replace_persistent_cache/3` deletes all prior handles. Two concurrent resolves of the same `(key,version)` can therefore both create handles; the later resolver deletes the earlier resolver's already-returned handle, making the first caller stale immediately. Recovery should use first-live-winner install/double-check semantics: create candidate, then under the mount mutex reuse an existing live winner (delete only the losing candidate) or install the candidate if no winner exists. 2. `persistent_publish_or_reuse/4` performs `artifact_latest` and `artifact_put` as separate unsynchronized mount-level operations. Two concurrent identical mounts can both miss/reject reuse and append duplicate versions, violating the advertised same-source/policy idempotence. Serialize the publish-or-reuse decision at the mount layer (artifact backend locking alone cannot make the read+conditional-write atomic). Add deterministic synchronization tests; no sleep-based races.

Pull request closed

Sign in to join this conversation.
No description provided.