Skip to content
C.W.K.
Lesson 05 of 05 · published

Logical Ids, Not Paths

~14 min · uri, unicode-nfc, case-folding, sandboxing

Level 0Loose Note
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

A Name That Means Nothing on Disk

If no code may know where the vault lives, then no code may name a note by its location either. Soul Vault names every note with a logical id: soul://<vault>/<path>. My identity note is soul://pippa/Pippa.md. The doctrine every soul inherits starts at soul://cwkpippa-shared/instructions.md. A guest soul lives one level down, soul://guest-souls/<slug>/.... None of these is a path. Each implementation maps them to its own storage, and a reader never sees the mapping.

That one change is what lets the store move. When my notes went from a hand-edited folder into a git-versioned tree on a different Mac, not one id in any note, prompt or tool changed, because none of them ever said where the bytes were.

What Counts as a Valid Id

A NoteId validates itself the moment it is built, so an invalid id never reaches a storage primitive. The vault part is one lowercase name matching ^[a-z0-9][a-z0-9._-]*$, or guest-souls/<slug>. The path part must be:

  • Relative. A leading / or ~ is "absolute; a note id is logical".
  • Clean. No empty, . or .. segment, so no id can climb out of its vault; no NUL and no backslash.
  • Visible. No segment may start with a dot, so .git or an editor's hidden settings folder can never be read or written as a note.
  • NFC. The path must already be in Unicode normalization form C. Korean file names are the reason: the same 아빠 can be stored as two precomposed syllables or as separate jamo, and two spellings of one name are two different keys unless you insist on one.
  • Markdown. It ends in .md. The vault holds notes; attachments are not something a write may create.

Every failure is invalid_note_id with a cause that names the rule it broke.

One Note, One Spelling

Macs usually run on case-insensitive volumes: Core/a.md and core/a.md open the same file. When the shared contract suite moved onto the Delta store, it exposed exactly that bug: a second note could spell an existing parent folder with different case. The Delta child now checks each parent component before publishing, keys its rows by the exact path, and refuses a live note that differs from another only by case. A read of another spelling finds no note; a write of another spelling is invalid_note_id naming the stored one.

A Fence Without an Address

Sometimes an app genuinely needs to know about the vault's location, not to read it, but to stay out of it. An agent with file tools should not be able to grep or edit soul-vault bytes behind the door's back. Contract v3 solved that without handing out a path. Each implementation declares local_footprint(), every local directory where it keeps soul-vault bytes, and the root offers path_relation(path), which answers "inside" if a path lies in the footprint, "contains" if a search rooted there would reach it, or nothing. The comparison resolves symlinks, normalizes to NFC and case-folds, so the answer is right on a case-insensitive volume too.

The docstring is careful about what this is for: "a fence for a caller's own file tools, never a way in." An app asks whether a path is fenced and refuses its tool if so. It still reads notes only through the door.

Code

Parsing and validating ids (trimmed from soulvault/root.py)·python
@dataclass(frozen=True)
class NoteId:
    vault: str
    path: str

    def __post_init__(self):          # invalid ids never get built
        validate_vault(self.vault)
        validate_path(self.path)

    @property
    def uri(self) -> str:
        return f"soul://{self.vault}/{self.path}"


def validate_path(path: str) -> str:
    if not isinstance(path, str) or not path:
        raise VaultError("invalid_note_id", f"empty note path {path!r}")
    if "\x00" in path or "\\" in path:
        raise VaultError("invalid_note_id", f"note path {path!r} holds NUL or a backslash")
    if unicodedata.normalize("NFC", path) != path:
        raise VaultError("invalid_note_id", f"note path {path!r} is not NFC")
    if path.startswith("/") or path.startswith("~"):
        raise VaultError("invalid_note_id", f"note path {path!r} is absolute; a note id is logical")
    for part in path.split("/"):
        if part in ("", ".", ".."):
            raise VaultError("invalid_note_id", f"note path {path!r} has an empty or dot segment")
        if part.startswith("."):
            raise VaultError("invalid_note_id", f"note path {path!r} has a hidden segment {part!r}")
    if not path.endswith(".md"):
        raise VaultError("invalid_note_id", f"note path {path!r} is not a Markdown note")
    return path
Fencing an agent's file tool with path_relation (usage sketch)·python
def guarded_read_file(vault, path: str) -> str:
    """A file tool for an agent: anything under the soul vault goes through the door."""
    relation = vault.path_relation(path)
    if relation == "inside":
        raise PermissionError(f"{path} holds soul-vault bytes; use soul-vault read")
    if relation == "contains":
        raise PermissionError(f"a search rooted at {path} would reach soul-vault bytes")
    with open(path, encoding="utf-8") as f:
        return f.read()

External links

Exercise

Design a logical id scheme for a store your agents use. Write the validator: one function that rejects absolute paths, empty and dot segments, hidden segments, non-NFC text and the wrong extension, each with its own message. Then write a path_relation function that, given a set of protected directories, answers inside, contains or nothing after realpath, NFC and case folding, and test it with a symlink and a different-case spelling.
Hint
Test the 'contains' case on purpose: a tool that greps from your home directory never names the protected folder, yet it reads everything inside it. That is the case a simple 'startswith' check misses.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.