Skip to content

feat(source): exclude active control directory and runtime state from observation and watchers - #2107

Draft
chris-beckman wants to merge 15 commits into
feat/PR01-unrestricted-project-rootsfrom
feat/PR04-control-dir-watcher-exclusion
Draft

chris-beckman wants to merge 15 commits into
feat/PR01-unrestricted-project-rootsfrom
feat/PR04-control-dir-watcher-exclusion

Conversation

@chris-beckman

@chris-beckman chris-beckman commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

PR04 Verification & Implementation Report (f47882af8f190397f6a3226ce78845e3df789f90)

  • PR: #2107 (https://github.com/paintedwolf-ai/paintedwolf-code/pull/2107, isDraft: true)
  • Branch: feat/PR04-control-dir-watcher-exclusion
  • Prerequisite Base: feat/PR01-unrestricted-project-roots (#2103 @ 8a0c5a3071f4ede07c4866a97a3361830f392c92), based on origin/main (7e21d294a5aacaa6218cfd3f39643513a3745750). Note: PR04 depends only on PR01 (#2103), not on PR03.
  • HEAD Commit SHA: f47882af8f190397f6a3226ce78845e3df789f90
  • HEAD Tree SHA (HEAD^{tree}): 029f702a528ed2c9338fcfb9d58526f5b2a41f5a

1. Disposition of decisions/006-pr03-pr04-follow-up.md

Item 1 — Single Authoritative Read-Floor Policy in confine (agent_read_floor.go)

  • Moved AgentReadFloor, CompileAgentReadFloor, and IsAgentSourceReadDenied out of lycaon/internal/enginepaths/observation_boundary.go and into lycaon/internal/confine/agent_read_floor.go.
  • Removed defaultHomeKeyMaterialRelPaths, effectiveAgentExtraDenyRoots, and splitEnvPathList from enginepaths/observation_boundary.go.
  • confine.CompileAgentReadFloor and confine.IsAgentSourceReadDenied now consume confine's authoritative:
    • configuredReadDenyRoots() in lycaon/internal/confine/read_floor.go (parsing LYCAON_SANDBOX_DENY_READ via filepath.SplitList / os.PathListSeparator and honoring secretReadDenyDisabled() opt-out tokens off, none, disable, disabled without treating opt-out tokens as relative path tokens),
    • KeyMaterialReadDenyPaths() (pack-sourced key-material read floor, which remains enforced even when the control-plane read floor is opted out),
    • controlPlaneReadDenyRoots() and EffectiveSessionDirsForRoot(root, sessionScratch, sessionOutput).
  • lycaon/internal/enginepaths/observation_boundary.go is now strictly focused on project-observation exclusion (IsObservedStateExcluded, IsObservedDirChildExcluded, IsControlRootRuntimeEntry, and pure lexical/canonical path helpers).

Item 2 — Managed-Workspace Carve-Out Restricted to Control-Directory Deny Root Only

  • In lycaon/internal/confine/agent_read_floor.go, lycaon/internal/confine/protected_root_intersection.go (underSecretReadDeny), and lycaon/internal/confine/write_root_grants.go (ControlPlanePathDenied), a managed workspace (<configdir>/session-worktrees/<p>/<s>, <configdir>/drafts/<id>, <configdir>/worker-branches/<p>/<j>, etc.) or invocation-owned session directory ONLY carves out the configdir.Root() / placement control-plane deny root, and never carves out explicit extra deny roots (configuredReadDenyRoots() from LYCAON_SANDBOX_DENY_READ) or KeyMaterialReadDenyPaths().
  • Verified by TestCompileAgentReadFloor_ManagedWorkspaceRespectsExplicitExtraDenyRoots (confine/agent_read_floor_test.go), TestCompileAndAssertReadScopeControlPlaneAndSessionScratch (sandbox/read_scope_test.go), TestHumanWarmedSharedCatalogNeverLeaksControlPlaneOrKeyMaterialToAgent (sourcecatalog/index_read_test.go), and TestDirectControlRootAttachmentWithHumanWarmedCatalogCannotExposeStateToAgentTools (tools/native/survey/scratch_freshness_test.go) across both subtree (<wt>/private) and whole-root (<wt>) LYCAON_SANDBOX_DENY_READ configurations, including on a human-warmed catalog.

Item 3 — Concrete Session Path Shape vs. Invocation Ownership

  • Removed explicitConfigSessionPrefix from lycaon/internal/enginepaths/observation_boundary.go and ensured EffectiveSessionDirsForRoot(root, sessionScratch, sessionOutput) in lycaon/internal/confine/write_root_grants.go has no fallback that infers authority from concrete <configdir>/scratch/<any-id> or <configdir>/session-output/<any-id> path shape.
  • <configdir>/scratch/<any-id> and <configdir>/session-output/<any-id> remain excluded by enginepaths.IsObservedStateExcluded even when attached directly as rootPath.
  • Invocation session ownership is threaded explicitly via sandbox.WithSessionDirs(ctx, tctx.Host.SessionScratchDir, tctx.Host.SessionOutputDir) and sandbox.SessionDirsFromContext(ctx) across tools/invocation_confinement.go, tools/projectpaths/projectpaths.go, tools/native/survey/{find,list_dir,grep,wc,stat,read,source_catalog}.go, sandbox/{read_scope,boundary_impl,survey_walk}.go, and sourcecatalog/{snapshot,index_store,index_agent_floor}.go.
  • Verified that @scratch (<configdir>/scratch/sess-1) and @output (<configdir>/session-output/sess-1) remain discoverable and readable for their owning invocation, while attaching another session's scratch directory (<configdir>/scratch/other-session) is excluded and denied across confine, sandbox, sourcecatalog, and all native survey/read tools.

Item 4 — Size-Budget Inspection & Architectural Disposition

File Pre-PR04 (8a0c5a3071) Prev PR04 (2aa240b691) Current PR04 (f47882af8f) Delta vs Prev PR04 Architectural Role
lycaon/internal/enginepaths/observation_boundary.go 0 (new) 798 460 -338 Pure observation/watcher exclusion boundary (IsObservedStateExcluded, IsObservedDirChildExcluded, IsControlRootRuntimeEntry)
lycaon/internal/confine/agent_read_floor.go 0 (new) 0 338 +338 Authoritative agent read-floor projection (AgentReadFloor, CompileAgentReadFloor, IsAgentSourceReadDenied)
lycaon/internal/sourcecatalog/index_store.go 600 667 586 -81 Core SQLite index store and IndexReader queries (14 lines below pre-PR04 baseline)
lycaon/internal/sourcecatalog/index_agent_floor.go 0 (new) 0 112 +112 IndexReader agent read-floor and private-path projection helpers
lycaon/internal/sourcecatalog/summary_write.go 513 521 521 0 Orientation summary builder (uses confine.CompileAgentReadFloor)
lycaon/internal/sourcecatalog/summary_read.go 485 487 487 0 Orientation summary cache & reader (keys summaryStore by floor.CacheKey())
lycaon/internal/sandbox/survey_walk.go 455 463 483 +20 Shared filesystem walk with IsObservedStateExcluded and invocation-owned session support

Shared-File Composition with PR03

PR04 shares the following files with PR03 using identical function signatures and semantics so PR01 -> PR03 -> PR04 (or PR01 -> PR04 -> PR03) merges cleanly:

  • lycaon/internal/confine/read_floor.go: configuredReadDenyRoots() and SecretReadDenyRoots().
  • lycaon/internal/confine/protected_root_intersection.go: underSecretReadDeny(path) checks configuredReadDenyRoots() before underSecretReadAllowBack(path).
  • lycaon/internal/confine/write_root_grants.go: ControlPlanePathDenied, EffectiveSessionDirsForRoot(root, sessionScratch, sessionOutput), and RootOverlapsControlPlaneReadFloor(root).
  • lycaon/internal/sandbox/context.go: WithSessionDirs(ctx, scratchDir, outputDir) and SessionDirsFromContext(ctx).
  • lycaon/internal/tools/invocation_confinement.go & lycaon/internal/tools/projectpaths/projectpaths.go: sandbox.WithSessionDirs(ctx, tctx.Host.SessionScratchDir, tctx.Host.SessionOutputDir).

2. Tree-Bound Remote Verification

Both remote verification runs executed against the exact tree of HEAD (f47882af8f190397f6a3226ce78845e3df789f90):

  • Local & Remote Git HEAD: f47882af8f190397f6a3226ce78845e3df789f90
  • Local & Remote Git HEAD^{tree}: 029f702a528ed2c9338fcfb9d58526f5b2a41f5a
  • macOS (m1@62.210.150.58) Receipt:
    • Batch ID: 76c4d458064f4bc688d72d05083535d6
    • Request ID: 00000000000000000002-f152a9d5efeb4569985d9c6dea2a56c2
    • Synthetic Snapshot source_commit: 88ee84d4e85188322675f7b4952308d020d70344
    • Synthetic Snapshot source_tree: 029f702a528ed2c9338fcfb9d58526f5b2a41f5a (exact match to HEAD^{tree})
    • Status: passed (exit_code: 0, 11 packages, 2300 passed, 0 failed, 64 skipped)
  • Linux (cdbeckman@10.0.20.5) Receipt:
    • Batch ID: cedbc7315e034327bd4b6836a39c6536
    • Request ID: 00000000000000000002-cd29470ef4514d288a3d7195f7e7d68b
    • Synthetic Snapshot source_commit: 4427e87be2e5e1b2a5896a33e93d4cc9e54a9a01
    • Synthetic Snapshot source_tree: 029f702a528ed2c9338fcfb9d58526f5b2a41f5a (exact match to HEAD^{tree})
    • Status: passed (exit_code: 0, 11 packages, 2619 passed, 0 failed, 15 skipped)

Per-Package Outcome Table (stage-001.json / stage-001-go.json)

Package macOS Status macOS Pass / Fail / Skip Linux Status Linux Pass / Fail / Skip
github.com/lycaon/lycaon/internal/confine pass 367 / 0 / 55 pass 584 / 0 / 9
github.com/lycaon/lycaon/internal/confine/linuxsandbox pass 22 / 0 / 0 pass 113 / 0 / 0
github.com/lycaon/lycaon/internal/enginepaths pass 17 / 0 / 0 pass 17 / 0 / 0
github.com/lycaon/lycaon/internal/projectsource pass 444 / 0 / 0 pass 444 / 0 / 0
github.com/lycaon/lycaon/internal/repochange pass 50 / 0 / 9 pass 61 / 0 / 6
github.com/lycaon/lycaon/internal/sandbox pass 145 / 0 / 0 pass 145 / 0 / 0
github.com/lycaon/lycaon/internal/sourcecatalog pass 595 / 0 / 0 pass 595 / 0 / 0
github.com/lycaon/lycaon/internal/sourcefeed pass 65 / 0 / 0 pass 65 / 0 / 0
github.com/lycaon/lycaon/internal/sourcescope pass 35 / 0 / 0 pass 35 / 0 / 0
github.com/lycaon/lycaon/internal/survey pass 47 / 0 / 0 pass 47 / 0 / 0
github.com/lycaon/lycaon/internal/tools/native/survey pass 513 / 0 / 0 pass 513 / 0 / 0
Total passed 2300 / 0 / 64 passed 2619 / 0 / 15

Key PR04 Acceptance Tests (stage-001-go.json)

Package Test Name macOS Linux
internal/confine TestCompileAgentReadFloor_ControlPlaneAndKeyMaterial pass (0.00s) pass (0.00s)
internal/confine TestCompileAgentReadFloor_OptOutAndPathListSeparator pass (0.00s) pass (0.00s)
internal/confine TestCompileAgentReadFloor_ManagedWorkspaceRespectsExplicitExtraDenyRoots pass (0.00s) pass (0.00s)
internal/confine TestCompileAgentReadFloor_SessionOwnershipRequired pass (0.00s) pass (0.01s)
internal/enginepaths TestIsObservedStateExcluded_StrictAncestorExcludesActiveControlDirAndPlacements pass (0.01s) pass (0.01s)
internal/enginepaths TestIsObservedStateExcluded_DirectAndChildControlDirAttachment pass (0.00s) pass (0.00s)
internal/enginepaths TestIsObservedStateExcluded_CanonicalAliasesSymlinksAndCase pass (0.00s) pass (0.00s)
internal/repochange TestPrivateTreeSuppressesOnlyItsOwnWatcherChanges pass (0.00s) pass (0.00s)
internal/sandbox TestCompileAndAssertReadScopeControlPlaneAndSessionScratch pass (0.00s) pass (0.00s)
internal/sourcecatalog TestHumanWarmedSharedCatalogNeverLeaksControlPlaneOrKeyMaterialToAgent pass (0.06s) pass (0.87s)
internal/tools/native/survey TestScratchSurveySeesWritesAfterEmptyInventory pass (0.01s) pass (0.01s)
internal/tools/native/survey TestDirectControlRootAttachmentWithHumanWarmedCatalogCannotExposeStateToAgentTools pass (0.06s) pass (0.05s)

3. Collected Artifact Paths & SHA256 Digests

All artifacts are stored in /Users/cdbeckman/.local/share/paintedwolf-unrestricted-project-roots/artifacts/PR04/f47882af8f190397f6a3226ce78845e3df789f90/ (and mirrored at /Users/cdbeckman/.local/share/paintedwolf-unrestricted-project-roots/artifacts/PR04/):

Artifact File SHA256 Digest
macos-test-digest-receipt.json 27ffd4531a1615b7ff2445f36ca0f94ea317165b3dd94bdcc35a982cb0e359fb
macos-test-digest-stage-001.json 2b0556fdbe8cd542b4bd43b90788e7f6166e8abe0eabd7c6f3563c8f3b981f69
macos-test-digest-stage-001-go.json 3317ed431cec8a833c6de53c3666fc7683799faf4dade2c8befa20162af7681f
macos-test-digest-stage-001.log e35cb260eb342cae6b2124ad44887cd6c9e74f6ebe75e6b1db472f3e2bc11d54
linux-test-digest-receipt.json ded197055ef551376d5258cf8f0600f5d571da01aaa1ab4513f88195369405ec
linux-test-digest-stage-001.json 4724deb8d45469440f48de0475b977de8d1f8d9d7e48af76d8f3ba552de3c1b9
linux-test-digest-stage-001-go.json f3c0c5613b9ea3f0612e032eb8c71719664fa3c00e4f522a2f5a95a8f168edd7
linux-test-digest-stage-001.log b24b9d28e7c7ca046a940c88f00e85e16013521c20061588e63a79d3180c3394

… policy (WIP)

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…ad floor across catalog and watchers

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…ndary tests

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
… seed live watcher subdirs

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…t read floor across survey tools

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…ssionPrefix

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…eshness_test

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…irect survey/catalog entrypoints on human-warmed control root

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
…ness_test

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
@chris-beckman
chris-beckman force-pushed the feat/PR04-control-dir-watcher-exclusion branch 5 times, most recently from f7daef8 to 4c4a0ec Compare November 10, 2026 12:14
…in confine and enforce session ownership

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>
@chris-beckman
chris-beckman force-pushed the feat/PR04-control-dir-watcher-exclusion branch from 4c4a0ec to f47882a Compare November 10, 2026 12:20
…protected floor

Signed-off-by: Chris Beckman <chris@paintedwolf.ai>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant