Repository navigation
land: confined-sessions - #485
Queued
aft-alfonso[bot] wants to merge 17 commits into
Queued
aft-alfonso[bot] wants to merge 17 commits into
aft-alfonso[bot] wants to merge 17 commits into
Conversation
ualtinok
added a commit
that referenced
this pull request
Oct 17, 2026
…485) Carry prior confinement into new frozen policy rows in the same transaction, while preserving scoped identity and incoming routing params. Disable both files-mode ignore loaders for confined outlines, and validate implicit Rust test-module candidates before metadata probes. Add epoch, auxiliary-read, and byte-compared golden regressions. Existing assertions retain their meaning. Verification: confined lib tests 21 passed; remote policy 39 passed with two runner-sandbox failures excluded (unfiltered run: 38 passed, 2 failed); remote DB 6 passed; outline 67 passed, 4 ignored; list envelope 181 passed; binary ignore fixtures 3 passed; Windows -D warnings check and rustfmt passed. Four non-vacuity controls each reddened exactly its expected regression test and were restored. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
ualtinok
added this pull request to the merge queue
Oct 17, 2026
Any commits made after this event will not be merged.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 17, 2026
ualtinok
added this pull request to the merge queue
Oct 17, 2026
Any commits made after this event will not be merged.
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Oct 17, 2026
ualtinok
added a commit
that referenced
this pull request
Oct 17, 2026
…485) Carry prior confinement into new frozen policy rows in the same transaction, while preserving scoped identity and incoming routing params. Disable both files-mode ignore loaders for confined outlines, and validate implicit Rust test-module candidates before metadata probes. Add epoch, auxiliary-read, and byte-compared golden regressions. Existing assertions retain their meaning. Verification: confined lib tests 21 passed; remote policy 39 passed with two runner-sandbox failures excluded (unfiltered run: 38 passed, 2 failed); remote DB 6 passed; outline 67 passed, 4 ignored; list envelope 181 passed; binary ignore fixtures 3 passed; Windows -D warnings check and rustfmt passed. Four non-vacuity controls each reddened exactly its expected regression test and were restored. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
ualtinok
force-pushed
the
land/confined-sessions
branch
from
October 17, 2026 14:42
f3a34c0 to
ad6a194
Compare
ualtinok
added a commit
that referenced
this pull request
Oct 17, 2026
…licy store (#485) remote_policy::confined() refused every call on a scoped route with a RouteBind principal whenever ctx.db() was None. Contexts whose persistence was never configured (the subc bridge test harness, unit-test contexts) therefore refused ordinary unconfined workers with confine_policy_unavailable: the scope_revocation integration failures and scoped_worker_watch_recovers_under_route_harness_before_replay. - Never-configured persistence (runtime state 0, no handle) answers unconfined: catalog refuses confine without a database and every route bind configures persistence, so no fence can exist there. - While aft.db is still opening after a bind, catalog fetches and tool calls on such routes wait in DatabaseWaits (10 s budget) instead of being refused; a failed or still-opening database keeps refusing. - The per-call check is read-only (store::confined: no IMMEDIATE transaction, no carried-forward row, no last_used write) and a key found confined is remembered per context, so steady-state confined calls do no database work on the frame loop. - The confined-root containment check compared a policy_path identity (Windows verbatim prefix stripped) with a raw canonicalized root (verbatim), so on Windows nothing was inside the root; both sides now use the same normal form. confined_restart_and_fail_closed changed: a context that already read the fence now keeps it after the row is corrupted (still fenced, so an escape is path_outside_confined_root). The unreadable-row refusal is asserted on a fresh context, which is the process-restart case the assertion protects. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
ualtinok
added a commit
that referenced
this pull request
Oct 17, 2026
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Persist confine=root in the existing full scoped catalog key and enforce it on every call, including after database reopen. Serve only read, grep, glob and aft_outline; fence canonical paths before grants or consent and bypass borrowed indexes with bounded root walks. Capture 28 real-handler vectors with a byte-comparison test. Deliberately extend the role.describe conformance fixture with tool-catalog-confine/v1. New confinement tests, path/access/catalog regressions, list envelopes, and Windows compile pass; two existing remote-policy sandbox tests reproduce their runner failure on unchanged HEAD. Six named mutation controls redden and restore cleanly. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Explicitly review the existing read filePath spelling in the confined preflight, its legacy-wire test helper, and the captured catalog vector. Preserve the real-handler bytes rather than regenerating or broadening the publication vocabulary allowlist. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Inherit the request fence in every Rayon fallback scan callback, not just bounded filesystem helpers. Add real-handler coverage for filter-only outside-shaped patterns, helper-thread symlink refusals, permitted in-root file links, and walks encountering outside links. Capture nine additional golden replies without changing the original vectors. Pattern inputs do not select walk roots, so those requests remain safe complete filesystem replies rather than new refusals. Five named mutation controls redden and restore cleanly; required Rust, envelope, and Windows compile gates pass, excluding the two previously proven runner sandbox baseline failures. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…485) Carry prior confinement into new frozen policy rows in the same transaction, while preserving scoped identity and incoming routing params. Disable both files-mode ignore loaders for confined outlines, and validate implicit Rust test-module candidates before metadata probes. Add epoch, auxiliary-read, and byte-compared golden regressions. Existing assertions retain their meaning. Verification: confined lib tests 21 passed; remote policy 39 passed with two runner-sandbox failures excluded (unfiltered run: 38 passed, 2 failed); remote DB 6 passed; outline 67 passed, 4 ignored; list envelope 181 passed; binary ignore fixtures 3 passed; Windows -D warnings check and rustfmt passed. Four non-vacuity controls each reddened exactly its expected regression test and were restored. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…licy store (#485) remote_policy::confined() refused every call on a scoped route with a RouteBind principal whenever ctx.db() was None. Contexts whose persistence was never configured (the subc bridge test harness, unit-test contexts) therefore refused ordinary unconfined workers with confine_policy_unavailable: the scope_revocation integration failures and scoped_worker_watch_recovers_under_route_harness_before_replay. - Never-configured persistence (runtime state 0, no handle) answers unconfined: catalog refuses confine without a database and every route bind configures persistence, so no fence can exist there. - While aft.db is still opening after a bind, catalog fetches and tool calls on such routes wait in DatabaseWaits (10 s budget) instead of being refused; a failed or still-opening database keeps refusing. - The per-call check is read-only (store::confined: no IMMEDIATE transaction, no carried-forward row, no last_used write) and a key found confined is remembered per context, so steady-state confined calls do no database work on the frame loop. - The confined-root containment check compared a policy_path identity (Windows verbatim prefix stripped) with a raw canonicalized root (verbatim), so on Windows nothing was inside the root; both sides now use the same normal form. confined_restart_and_fail_closed changed: a context that already read the fence now keeps it after the row is corrupted (still fenced, so an escape is path_outside_confined_root). The unreadable-row refusal is asserted on a fresh context, which is the process-restart case the assertion protects. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…de (#485) A confined catalog offers outline as aft_outline on every route kind, but only the tool-provider v1 branch mapped that name back to the internal outline command. Legacy-route workers calling the offered name got unknown_tool in classic mode and call_mode_unsupported in codemode/flow. The confinement admission and rename now run in one helper before any name-based admission step on both route kinds; the later duplicate confinement lookup is removed so a legacy call still reads the policy once. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…licy (#485) A confined grep, glob or outline over a directory checked only the named directory against the read policy. Its walk then handed every child file to the searcher or parser unchecked, so a sandbox.read_deny file (or anything under a denied directory) inside the pinned root was opened and its text returned. Each confined call now snapshots the access policy that read applies to a single path (inside the confined root, no denial floor) into its thread-local fence, which helper threads inherit. The grep/glob walk and both outline walks check each file and directory against it before the entry is yielded or opened; a denied path is skipped and counted, and the reply reports the count as skipped_denied plus a text note (the path's name is never reported). Confined walks also honor the pinned tree's own ignore files again (.aftignore/.ignore/.gitignore for grep and glob, .ignore/.gitignore for an outline directory, .gitignore for outline files mode, matching each unconfined walk's families and precedence). A rule file counts only when it is a regular file inside the root that the read policy admits, and it is opened without following links; global excludes, .git/info/exclude, parent-directory and symlinked rule files are still never read. New golden vectors deny-{grep,glob,outline-directory,outline-files}.json record the replies for a tree with a denied file and a denied directory; no existing vector changed. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…485) glob_root checks confinement before the explicit-session task-output shortcut, which lists a session's own bash output files from outside the pinned root. The new test shows the shortcut answers for the owning session when unconfined, takes the confined walk ("fallback") when confined, and that a routed confined glob naming the output file or its folder is refused without listing any task output. The task-output setup used by the owned-outputs test moves into a shared helper; glob gains a unix test-only helper that exposes one glob_root discovery's files and source. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…gate (#485) A confined grep, glob or outline walk checked every entry below its starting path against the confined root and the read policy, but not the starting path itself: the walker never filters its own root, and grep reads a named file directly. Real calls were protected only by the request's earlier path check, so a caller that skipped it could walk or read outside the fence. The fence now checks the starting path itself (confinement::admits_walk_root): - the grep/glob walker builder returns None for a refused root, and every walk, scope probe and scope count treats that as an empty walk, before stat-ing the path; - grep's per-root search returns an empty result before its named-file read; - the outline discovery used by directory and files modes returns an empty, complete discovery. The grep, glob and outline handlers also refuse such a root with the confined path check's refusal (path_outside_confined_root), and a confined grep no longer takes the session task-output shortcut, as glob already does. The whole-project walker used only by semantic search keeps no starting-path check; confined calls never run semantic search. Tests: confined_walks_refuse_a_starting_path_outside_the_fence calls the glob, grep and outline walks directly (no earlier path check) at outside, symlinked-outside and read-denied starting files and directories. It asserts they find nothing, visit nothing, record no denied entries and open no watched file, and that the pinned root still walks. The explicit-session glob test now also asserts that the confined call finds and visits nothing. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
ualtinok
force-pushed
the
land/confined-sessions
branch
from
October 17, 2026 19:07
ad6a194 to
50f15c7
Compare
…ore rules in the root's spelling (#485) Frame-loop admission no longer takes the aft.db lock. When memory cannot answer a scoped worker's saved confinement, DatabaseWaits::read_confinement runs remote_policy::confined on a blocking thread and the call resumes carrying the result in DecodedFrame::confinement; admission stays closed until then, and a call with neither a memory answer nor a read result is refused (confine_policy_unavailable), never run unconfined. The confined-only memory cache is unchanged, so a cache hit stays synchronous. Direct handle_tool_call test callers (tool_provider route tests, consent tests, the remote-run restart test) now run the same read first, as the frame loop does. ConfinedIgnoreRules::is_ignored normalizes its local copy of the walked path, so a walk from a canonical Windows path (\\?\C:\...) finds the directory chain under the gate's prefix-stripped root. Two read-argument fixtures in persistence tests use the canonical `path` instead of `filePath`: neither dispatches a read, and the legacy vocabulary audit does not allowlist them. The confine_tests `call` helper moved to remote_policy_tests so a cross-platform glob test can use it. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…rk the key confined before commit (#485) Only "confined" was remembered, so every call from an ordinary unconfined scoped worker read aft.db off the frame loop and waited for the aft.db mutex, stalling behind any catalog job holding it. AppContext now keeps a PolicyConfinementMemo: confined keys (always winning) and unconfined keys, the latter tied to the aft.db handle they were read from (a different or reopened database drops them) and to a generation counter, so a read that started before a confining save is not remembered after it. db::remote_exec::freeze takes a `before_confining_commit` hook that runs inside the transaction whenever the saved row will carry the fence, whether asked for or carried forward from an older epoch. The catalog fetch passes remember_confined_policy_key, which marks the key confined and forgets every unconfined answer (a fence on one epoch can confine another epoch's key that has no row of its own) before the fence commits. The memo never waits for the database handle slot (try_db), so the frame loop does not stall behind an open. awaits_policy_store now treats any remembered answer as known. confine_answer_is_kept_once_confined is unchanged and still holds: after clear_db the ordinary worker's unconfined answer no longer counts, because it belonged to the cleared handle. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…membered (#485) A worker's later epoch with no row of its own reads the older epoch's row. If the older epoch's confine save commits between that read and the remember call, the generation check in remember_unconfined_policy_key is what keeps the stale "unconfined" answer out of memory; nothing tested it. A test-only hook (AppContext::after_next_policy_read_for_test, run by remote_policy::confined between the aft.db read and the remember call) lets the new test drive exactly that interleaving: the later epoch's next admission reads aft.db again, is confined, and refuses bash. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
…485) Probe the database handle slot without waiting on the frame loop. Retry policy reads across confinement-generation changes, bounded to three attempts before refusing admission. The race regression now expects Ok(true), not the former stale Ok(false), because the racing call itself must refuse bash. Retain its memo generation-guard assertion independently using a delayed later-epoch read, and cover direct admission, slot contention and retry exhaustion. Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com>
ualtinok
added this pull request to the merge queue
Oct 18, 2026
Any commits made after this event will not be merged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Accepted agent delivery; branch-keyed search descriptor included.
Summary by cubic
Adds
confine: "root"support to scoped worker catalogs, keeping a read-only worker session inside its own checkout even after an AFT restart or catalog epoch rebind. Previously a worker catalog could only request remote execution routing; now it can opt into a root-only filesystem surface.read,grep,glob, andaft_outlineon every route and call mode; all other tools are refused withtool_not_offered_confined.path_outside_confined_rootwithout permission prompts, regardless of grants, owned bash outputs, or symlink targets.skipped_denied, never opened or returned.Written for commit e402cbd. Summary will update on new commits.


Confidence Score: 5/5
No new blocking issue was established in the changes since the previous review.Summary
Adds saved, root-only worker sessions with four read-only tools.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Worker tool call] --> B{Policy known in memory?} B -->|No| C[Read saved policy off the frame loop] C --> D{Policy changed during read?} D -->|Yes| E[Retry up to three times] E --> C D -->|No| F[Check offered tool] B -->|Yes| F F --> G{Confined?} G -->|Yes| H[Check paths and apply root fence] G -->|No| I[Normal dispatch] H --> J[Read-only dispatch]Reviews (6) · Last reviewed commit: "mason: keep confinement reads nonblockin..." · Reviewed by Greptile