Give each collection a fail-closed transport boundary that follows its content through inference, publishing, exports, Capsules, and traces.
Each collection has a fail-closed transport policy (local_only, lan, remote) evaluated at transfer time, not at config time. Derived artifacts inherit the most restrictive source policy: one local_only passage makes a Capsule local_only. Most real setups are mixed: your open notes are fine on a LAN inference box, the client engagement is not, and the vendor contracts should never reach a remote endpoint under any circumstances.
Egress is distinct from source availability: availability (sourceAvailability: any|local) controls whether source content may be materialized during indexing on supported macOS File Provider layouts; egress controls where derived data may travel. See Source availability.
| Policy | Content may reach |
|---|---|
local_only | This machine. Loopback destinations only. No LAN model server, no remote inference, no publishing. |
lan | This machine plus private-network destinations, such as a GPU box in the same network. |
remote | Any configured destination, including a remote inference endpoint or gno.sh publishing. |
Policies loosen in the order local_only → lan → remote. A collection without an explicit policy is local_only, and upgrading GNO never widens a policy.
gno collection policy get notes
gno collection policy check --action export --destination remote \
--content-class retrieval_trace --collection notes \
--authenticated --authorized --explain-egressget shows the effective policy, where it came from, and its numeric revision. check is a dry run: it answers “would this specific transfer be allowed” without attempting it, which is what you want in CI, in a runbook, or before wiring up a remote model. --explain-egress shows the reasoning.
gno collection policy set notes local_only
gno collection policy set notes remote --confirm-relaxation 7Tightening is immediate. Loosening is never inferred: run get, look at the current value and its source, then pass that exact numeric revision to --confirm-relaxation. The revision is durable and single-use. Stale, replayed, cross-collection, and cross-target confirmations all fail closed.
That handshake exists so a relaxation cannot be scripted blind. You have to have read the current state, in this collection, at this revision, to widen it. An agent that automates policy set without a fresh get simply fails.
Tightening stops sessions, streams, and queued jobs that started under the looser policy. Retry them to run under the new policy.
A policy that applied only to the original file would be trivially escaped: build a Capsule from a restricted collection, then publish the Capsule.
Instead, derived artifacts inherit source policy. Snippets, embeddings, Context Capsules, retrieval traces, adapter records, and exports all carry the policy of what they came from. When evidence mixes several collections, the most restrictive participating policy governs the whole thing. One local_only passage in a Capsule makes that Capsule local_only.
Each Capsule evidence item exposes its own egress value and lineage, and the aggregate lineage must account for every contributing collection, so the boundary is inspectable rather than implicit.
Authentication, write authorization, and egress are separate checks, and passing one never satisfies another:
A valid bearer token on a non-loopback daemon still cannot pull local_only content off the machine.
One egress decision covers at most 64 collections, and one policy lineage at most 128. The limits apply to what a request covers, not to how many collections you configure. A request that names its collections is judged on just those. A request with no collection scope covers every configured collection, so with more than 64 configured it is denied with COLLECTION_LIMIT_EXCEEDED, as is a request naming more than 64. Over the resident HTTP MCP endpoint that is an ordinary HTTP 403 egress denial naming no collection, never a server error; unscoped MCP sync and daemon status deny the same way. Name the collections the request needs and try again. See Collection limits for the policy-check limits.
Classification is conservative by design and covers loopback, LAN, VPN, proxy, redirect, and remote destinations. Cases that would otherwise be laundering routes fail closed: DNS rebinding, redirects to a different class than the one you checked, mixed evidence, active streams, queued jobs, and stale relaxation confirmations.
Denial reasons are stable and content-free across CLI, REST, MCP, SDK, and Web. An error message never leaks the passage it refused to send, the path it came from, or a credential.
gno egress-audit list
gno egress-audit show <audit-id>
gno egress-audit status
gno egress-audit delete <audit-id>
gno egress-audit purgeReceipts are local, bounded, and redacted: what action was attempted, against what destination class, with what outcome. Never the content. Audit inspection and deletion keep working even when an outbound action was denied, so a refusal is always diagnosable.
# Client work never leaves the laptop
gno collection policy set client-engagement local_only
# Personal notes may use the GPU box on the LAN
gno collection policy get notes # note the revision
gno collection policy set notes lan --confirm-relaxation 3
# Verify before wiring a remote endpoint
gno collection policy check --action remote_inference --destination remote \
--content-class passage --collection notes --explain-egressOne caution worth repeating: egress policy governs transport, not authorization to use content. It stops bytes crossing a boundary. It does not encode who is allowed to read what, and it is not a substitute for keeping confidential material in the right collection in the first place.