import assert from 'node:assert/strict' import { test } from 'node:test' import { createFacetPolicy } from '../lib/policy.js' /** A stand-in for the agent shape the policy reads: session id plus working directory. */ const agentAt = (cwd, id = 's1') => ({ session: { id, header: { cwd } } }) test('the scope is the session directory, and nothing else', () => { const policy = createFacetPolicy({ mode: 'ask' }) const scope = policy.scopeFor(agentAt('/w/src/controllers/room')) assert.equal(scope.root, '/w/src/controllers/room') assert.equal(scope.cwd, scope.root) assert.deepEqual(scope.extras, []) }) test('an agent with no working directory has no facet', () => { const policy = createFacetPolicy({ mode: 'ask' }) assert.equal(policy.scopeFor(undefined), undefined) assert.equal(policy.scopeFor({}), undefined) assert.equal(policy.scopeFor({ session: { header: {} } }), undefined) assert.equal(policy.scopeFor({ session: { header: { cwd: ' ' } } }), undefined) assert.equal(policy.scopeFor({ session: { header: { cwd: 7 } } }), undefined) }) test('a grant widens one session and leaves every other alone', () => { const policy = createFacetPolicy({ mode: 'ask' }) policy.grant('s1', ['../shared'], '/w/src/room') assert.deepEqual(policy.scopeFor(agentAt('/w/src/room', 's1')).extras, ['/w/src/shared']) assert.deepEqual(policy.scopeFor(agentAt('/w/src/room', 's2')).extras, []) assert.deepEqual(policy.grantsFor('s1'), ['/w/src/shared']) assert.deepEqual(policy.grantsFor('s2'), []) }) test('grants are absolute, deduplicated and order-preserving', () => { const policy = createFacetPolicy({ mode: 'ask' }) // `shared` is relative to the session directory, `/w/src/shared` is not, and // both are kept: they are different places that happen to share a name. policy.grant('s1', ['shared', '/w/src/shared', 'shared', 'other'], '/w/src/room') assert.deepEqual(policy.grantsFor('s1'), ['/w/src/room/shared', '/w/src/shared', '/w/src/room/other']) }) test('revoke and reset narrow without touching anyone else', () => { const policy = createFacetPolicy({ mode: 'ask' }) policy.grant('s1', ['a', 'b'], '/w') policy.grant('s2', ['c'], '/w') assert.deepEqual(policy.revoke('s1', ['a'], '/w'), ['/w/b']) assert.deepEqual(policy.revoke('s1', ['b'], '/w'), []) assert.equal(policy.sessions.includes('s1'), false) assert.deepEqual(policy.grantsFor('s2'), ['/w/c']) policy.grant('s3', ['d'], '/w') assert.deepEqual(policy.reset('s3'), []) assert.deepEqual(policy.grantsFor('s3'), []) }) test('a disposed session does not leave its grant behind', () => { const policy = createFacetPolicy({ mode: 'ask' }) policy.grant('s1', ['shared'], '/w') assert.deepEqual(policy.sessions, ['s1']) policy.forget('s1') assert.deepEqual(policy.sessions, []) // A resumed session id must not inherit a grant nobody remembers making. assert.deepEqual(policy.scopeFor(agentAt('/w', 's1')).extras, []) }) test('standing grants resolve against each session, not the process', () => { const policy = createFacetPolicy({ mode: 'ask', extraRoots: ['../shared', '/abs/common'] }) assert.deepEqual(policy.scopeFor(agentAt('/w/a/one')).extras, ['/w/a/shared', '/abs/common']) assert.deepEqual(policy.scopeFor(agentAt('/w/b/two')).extras, ['/w/b/shared', '/abs/common']) }) test('junk in the configuration and in a grant is dropped, not thrown', () => { const policy = createFacetPolicy({ mode: 'ask', extraRoots: ['ok', '', ' ', 42, null] }) assert.deepEqual(policy.standing, ['ok']) policy.grant('s1', ['', ' ', 42, null, 'real'], '/w') assert.deepEqual(policy.grantsFor('s1'), ['/w/real']) }) test('standing grants and session grants are merged without repetition', () => { const policy = createFacetPolicy({ mode: 'ask', extraRoots: ['shared'] }) policy.grant('s1', ['shared', 'other'], '/w') assert.deepEqual(policy.scopeFor(agentAt('/w', 's1')).extras, ['/w/shared', '/w/other']) }) test('prompt placements are carried, so the preset row needs no config', () => { assert.equal(createFacetPolicy({ mode: 'ask' }).sectionOrder, 100) assert.equal(createFacetPolicy({ mode: 'ask' }).contextOrder, 105) assert.equal(createFacetPolicy({ mode: 'ask', sectionOrder: 7, contextOrder: 8 }).sectionOrder, 7) assert.equal(createFacetPolicy({ mode: 'ask', sectionOrder: 7, contextOrder: 8 }).contextOrder, 8) }) // ── per-session scope ──────────────────────────────────────────────────────── // // The declaration is the user's, written from the browser panel; the narrowing // is the agent's, held in memory. Everything below is about which of the two // wins, and the answer is always "the narrower one, and never a stale agent // focus that the user has since narrowed past". test('a declared focus narrows the root, and the session directory stays the base', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src/controllers/room' } }, }) const scope = policy.scopeFor(agentAt('/w', 's1')) assert.equal(scope.root, '/w/src/controllers/room') // `cwd` is still where relative paths resolve from — a declaration written // against the session keeps its meaning whatever the session focuses on. assert.equal(scope.cwd, '/w') }) test('a declaration is read live, so the panel needs no remount', () => { const scopes = {} const policy = createFacetPolicy({ mode: 'deny', scopes }) assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w') // What a panel write does to the running reference. scopes.s1 = { focus: 'src/module' } assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/module') }) test('declared extras join the deployment seed and the session grants', () => { const policy = createFacetPolicy({ mode: 'deny', extraRoots: ['seed'], scopes: { s1: { focus: 'src/room', extras: ['shared', '/abs/common'] } }, }) policy.grant('s1', ['granted'], '/w') assert.deepEqual(policy.scopeFor(agentAt('/w', 's1')).extras, [ '/w/seed', '/w/shared', '/abs/common', '/w/granted', ]) }) test('an agent narrowing is honoured while it stays inside the declared root', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src' } } }) policy.narrowTo('s1', '/w/src/room') assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/room') assert.equal(policy.narrowFor('s1'), '/w/src/room') }) test('a narrowing the user has since narrowed past is dropped, not honoured', () => { const scopes = { s1: { focus: 'src' } } const policy = createFacetPolicy({ mode: 'deny', scopes }) policy.narrowTo('s1', '/w/src/room') assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/room') // The user narrows to a sibling: the agent's focus is now outside the scope // they declared. Honouring it would put the session back where the user had // just decided it should not be. scopes.s1 = { focus: 'src/other' } assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/other') }) test('reset and forget drop the agent side and leave the declaration alone', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src', extras: ['shared'] } } }) policy.narrowTo('s1', '/w/src/room') policy.grant('s1', ['granted'], '/w') policy.reset('s1') // Back to what the user declared — narrower than the session, never wider. assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src') assert.deepEqual(policy.grantsFor('s1'), []) assert.deepEqual(policy.scopeFor(agentAt('/w', 's1')).extras, ['/w/shared']) policy.narrowTo('s1', '/w/src/room') policy.forget('s1') assert.equal(policy.narrowFor('s1'), undefined) assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src') }) test('junk in a declaration is ignored rather than thrown on', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: ' ', extras: ['ok', '', 42, null] }, s2: 'not an object' }, }) assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w') assert.deepEqual(policy.scopeFor(agentAt('/w', 's1')).extras, ['/w/ok']) assert.equal(policy.scopeFor(agentAt('/w', 's2')).root, '/w') }) // ── delegated sessions ─────────────────────────────────────────────────────── // // A session spawned or forked from this one has no declaration of its own, and // the user's declaration is keyed by session id. What stops it starting wider // than its parent is the parent's own resolved root, recorded as the parent // resolves a scope — which it does for every model request and every tool call, // so the record is always there before it can spawn anything. /** An agent whose header carries the lineage the harness writes for a child. */ const spawned = (id, parentSession, cwd = '/w') => ({ session: { id, header: { cwd, parentSession } }, }) test('a session spawned from a scoped one inherits its root, at any depth', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src/room' } } }) assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/room') const child = spawned('c1', 's1') assert.equal(policy.scopeFor(child).root, '/w/src/room') // Relative paths still resolve from the child's own directory, as for anyone. assert.equal(policy.scopeFor(child).cwd, '/w') assert.equal(policy.scopeFor(spawned('g1', 'c1')).root, '/w/src/room') }) test('a child is never wider than its parent is now, narrowing included', () => { const policy = createFacetPolicy({ mode: 'deny' }) policy.narrowTo('s1', '/w/src/room') // The narrowing is the agent's own decision; spawning a child is not a way to // undo it, because the root the child inherits already includes it. assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/room') assert.equal(policy.scopeFor(spawned('c1', 's1')).root, '/w/src/room') }) test('a declaration for the child itself stands over what it would inherit', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src/room' }, c1: { focus: '.' } } }) policy.scopeFor(agentAt('/w', 's1')) // Inheritance answers the case where the user made no statement about this // session; a statement they did make is the more specific one and stands. assert.equal(policy.scopeFor(spawned('c1', 's1')).root, '/w') }) test('a root is kept while a session that inherited it is still alive', () => { const policy = createFacetPolicy({ mode: 'deny', scopes: { s1: { focus: 'src/room' } } }) const child = spawned('c1', 's1') policy.scopeFor(agentAt('/w', 's1')) policy.scopeFor(child) // Disposing the parent must not quietly widen the child that is still running. policy.forget('s1') assert.equal(policy.scopeFor(child).root, '/w/src/room') // With nothing left to inherit it, the record goes too. policy.forget('c1') assert.equal(policy.scopeFor(agentAt('/w', 's1')).root, '/w/src/room') })