Namespaces
A namespace is a path. It is the HKDF info string for a key, the unit of a grant, and the line on the key rail.
ns()
import { ns } from "satchel-sdk";
const memory = ns("memory/assistant"); // ok
ns("Memory/Assistant"); // throws INVALID_NAMESPACE: uppercase
ns("memory//assistant"); // throws INVALID_NAMESPACE: empty segmentA path is 3 to 64 characters of lowercase letters and digits joined by / or -. ns() returns a branded string, so passing a raw string where a Namespace is expected is a type error. See ns.
What separates two namespaces
satchel/ns/memory/assistant and satchel/ns/memory/notes are different HKDF info strings, so they derive unrelated keys from the same passkey output. A record sealed under one opens under the other zero times: its header carries the keccak256 hash of the info string, and unseal compares it with the namespace you asked for before decrypting. The mismatch is a named error, NAMESPACE_MISMATCH, not a decryption failure. The proof page triggers it from a real wrong-namespace call.
What each app owns
One convention keeps apps from colliding: the first segment is the app or domain, the rest names the data.
| Namespace | Owned by | Holds |
|---|---|---|
memory/assistant | Pip, readable by Fern through a grant | what the assistant remembers about you |
memory/notes | Pip | a second folder under the same passkey, used on the proof page |
trustset/notes | trustset | sealed notes, see the migration guide |
Fingerprints
fingerprint(ns) returns eight hex characters of SHA-256 over a fixed label and the namespace key. It is safe to show and compare: two devices that hold the same passkey print the same value for the same namespace.