M7.1: extract nisse-core
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m42s
CI / e2e (pull_request) Successful in 4m0s

What isn't the dev agent's moves to nisse-core, unchanged in behaviour,
with its tests: canonical, render, render_anthropic, llm, catalog, probe,
context, views, state, reconcile, worker, cancel, dump, transport, matrix,
execution, testing, vault, memory, oplog, envfile and gitea (whole).

- nisse_core.state.StateStore holds the tables every agent has; an
  agent's subclass adds its tables (version, migrations), roles and
  views. nisse_dev.state.StateStore adds the build VM blocks (v5).
- views.project takes what each role is shown, rather than a fixed table.
- Vault takes the agent's name (agent=), for provenance.
- A test that nisse_core never imports an agent.
- The loop, config, git, delegation, expert, tools and evals stay in
  nisse-dev until nisse-ops has a loop to share.

Docs: the Development Plan marks M7.1 done and notes that Phase 6 waits on
the agent host and Terraform; Architecture Part L names the package.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-13 18:37:01 +00:00
1 parent 4bbcb09c8e
commit 154a0c3fa8
119 files changed
+1086 -912

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
"""The knowledge vault (L10, M5.1): only configured folders exist, paths
can't escape them, read-only folders refuse writes, writes are atomic and
record provenance, and the editor's own files are never notes."""
import os
from datetime import UTC, datetime
from pathlib import Path
import pytest
import yaml
from nisse_core import vault as vault_module
from nisse_core.vault import Vault, VaultError, split_frontmatter
@pytest.fixture
def vault(tmp_path: Path) -> Vault:
root = tmp_path / "vault"
(root / "notes" / "deep").mkdir(parents=True)
(root / "notes" / "Procedures.md").write_text("# Procedures\n\nRestart the hub.\n")
(root / "notes" / "deep" / "Hub.md").write_text(
"The hub is in the shed.\nhub hub\n"
)
(root / "notes" / ".silverbullet").mkdir()
(root / "notes" / ".silverbullet" / "state.md").write_text("hub")
(root / "notes" / "_plug").mkdir()
(root / "notes" / "_plug" / "x.md").write_text("hub")
(root / "private").mkdir()
(root / "private" / "Secret.md").write_text("the hub password")
(root / "memory" / "nisse-ops").mkdir(parents=True)
(root / "memory" / "nisse-ops" / "Ops.md").write_text("ops hub")
return Vault(
root,
{"memory/nisse-dev": "rw", "notes": "ro"},
agent="nisse-dev",
clock=lambda: datetime(2026, 10, 8, 12, 0, tzinfo=UTC),
)
def test_only_configured_folders_exist(vault: Vault) -> None:
assert vault.read("notes/Procedures.md").startswith("# Procedures")
for path in ("private/Secret.md", "memory/nisse-ops/Ops.md", "Top.md"):
with pytest.raises(VaultError, match="not in a folder you can see"):
vault.read(path)
assert vault.notes() == ["notes/Procedures.md", "notes/deep/Hub.md"]
assert vault.notes("notes/deep") == ["notes/deep/Hub.md"]
@pytest.mark.parametrize(
"path",
["/etc/passwd", "notes/../private/Secret.md", "", "notes\\x.md", "../x.md"],
)
def test_paths_cant_escape(vault: Vault, path: str) -> None:
with pytest.raises(VaultError):
vault.read(path)
def test_a_link_out_of_its_folder_is_refused(vault: Vault) -> None:
os.symlink(vault.root / "private" / "Secret.md", vault.root / "notes" / "Ln.md")
with pytest.raises(VaultError, match="leads outside its folder"):
vault.read("notes/Ln.md")
assert "notes/Ln.md" not in vault.notes()
assert all(f.path != "notes/Ln.md" for f in vault.search("password"))
def test_the_editors_files_are_not_notes(vault: Vault) -> None:
with pytest.raises(VaultError, match="editor's own files"):
vault.read("notes/.silverbullet/state.md")
with pytest.raises(VaultError, match="editor's own files"):
vault.read("notes/_plug/x.md")
with pytest.raises(VaultError, match="Markdown"):
vault.read("notes/deep")
def test_search_ranks_matches_and_sees_only_what_it_may(vault: Vault) -> None:
found = vault.search("HUB")
assert [f.path for f in found] == ["notes/deep/Hub.md", "notes/Procedures.md"]
assert found[0].lines == ("The hub is in the shed.", "hub hub")
with pytest.raises(VaultError):
vault.search(" ")
def test_a_long_note_is_cut_and_says_so(vault: Vault) -> None:
(vault.root / "notes" / "Long.md").write_text("x" * 30_000)
text = vault.read("notes/Long.md")
assert text.endswith("[10000 more characters not shown]")
def test_read_only_folders_refuse_writes(vault: Vault) -> None:
with pytest.raises(VaultError, match="read-only"):
vault.write("notes/New.md", "hi", provenance={"source": "t"})
assert not (vault.root / "notes" / "New.md").exists()
def test_a_write_records_provenance_and_keeps_the_rest(vault: Vault) -> None:
text = "---\ntags: [hub]\naliases: [h]\n---\n# Hub\n\nIn the shed.\n"
vault.write(
"memory/nisse-dev/sub/Hub.md",
text,
provenance={"source": "matrix", "work_item": "sandbox-14"},
)
written = (vault.root / "memory" / "nisse-dev" / "sub" / "Hub.md").read_text()
front, body = split_frontmatter(written)
assert front["tags"] == ["hub"] and front["aliases"] == ["h"]
assert front["provenance"] == {
"by": "nisse-dev",
"source": "matrix",
"work_item": "sandbox-14",
"written": "2026-10-08T12:00:00+00:00",
}
assert body == "# Hub\n\nIn the shed.\n"
assert vault.notes("memory/nisse-dev") == ["memory/nisse-dev/sub/Hub.md"]
def test_an_interrupted_write_leaves_the_old_note_and_no_litter(
vault: Vault, monkeypatch: pytest.MonkeyPatch
) -> None:
vault.write("memory/nisse-dev/N.md", "old\n", provenance={"source": "t"})
folder = vault.root / "memory" / "nisse-dev"
before = (folder / "N.md").read_text()
def crash(src: str, dst: str) -> None:
raise OSError("disk gone")
monkeypatch.setattr(vault_module.os, "replace", crash)
with pytest.raises(VaultError, match="couldn't write it"):
vault.write("memory/nisse-dev/N.md", "new\n", provenance={"source": "t"})
assert (folder / "N.md").read_text() == before
assert sorted(p.name for p in folder.iterdir()) == ["N.md"]
def test_bad_frontmatter_is_refused(vault: Vault) -> None:
with pytest.raises(VaultError, match="valid YAML"):
vault.write(
"memory/nisse-dev/B.md", "---\n: [\n---\nx\n", provenance={"source": "t"}
)
with pytest.raises(VaultError, match="mapping"):
vault.write("memory/nisse-dev/B.md", "---\n- a\n---\nx\n", provenance={})
def test_read_write_folders_are_made(tmp_path: Path) -> None:
Vault(tmp_path / "v", {"memory/nisse-dev": "rw", "notes": "ro"}, agent="a")
assert (tmp_path / "v" / "memory" / "nisse-dev").is_dir()
assert not (tmp_path / "v" / "notes").exists() # not ours to make
assert yaml # frontmatter is YAML