feat(wiki): sync upstream v2026.10.4 core maintenance

Selectively port source hashing, locked manifest/memory writes, path-aware
link lint and graph analysis, and revision-checked staging from upstream
4f457e154653756b04802d5aeb25c20f42b4770c. Record source hashes and exclusions.

Preserve local archive/catalog citations, curated manifest metadata, config
location, file-index and MCP behavior. Wire maintenance guidance into four
skills without migrating vault contents or changing running services.

Validation: 609 tests passed, including existing MCP/file-asset regressions
and imported upstream maintenance tests. Read-only vault status left shared
metadata hashes unchanged. Wiki MCP remains inactive; orders MCP is active.
This commit is contained in:
Codex
2026-10-31 12:39:48 +00:00
parent 1dd5d8d753
commit 82890606aa
35 changed files with 12632 additions and 44 deletions
+293
View File
@@ -0,0 +1,293 @@
"""Tests for the wiki clock (`obsidian_wiki.clock`) and the writers that use it.
The guarantees: a vault without ``timezone:`` gets byte-identical stamps to
before (UTC, ``Z``); a vault with one gets that zone's own offset on every
stamp, correct across a daylight-saving transition; bare dates are calendar
days in the zone; a misspelled zone fails the write instead of silently
writing UTC; and the docs name the knob.
"""
from __future__ import annotations
import re
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo
import pytest
from obsidian_wiki import clock
from obsidian_wiki import memory as mem
ROOT = Path(__file__).resolve().parent.parent
DETROIT = ZoneInfo("America/Detroit")
Z_STAMP = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$")
OFFSET_STAMP = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$")
DETROIT_STAMP = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-0[45]:00$")
BARE_DATE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
def _index(vault: Path, *extra: str) -> Path:
lines = ["---", "title: Wiki Index", *extra, "generated_by: obsidian-wiki memory index", "---", "", "# Wiki Index", ""]
path = vault / "index.md"
path.write_text("\n".join(lines), encoding="utf-8")
return path
@pytest.fixture()
def vault(tmp_path: Path) -> Path:
for name in ("concepts", "_meta"):
(tmp_path / name).mkdir()
(tmp_path / "concepts" / "attention.md").write_text(
"---\ntitle: Attention\nsummary: Core block\ntags: [ml]\nupdated: 2026-09-01\n---\n\n# Attention\n\nBody.\n",
encoding="utf-8",
)
_index(tmp_path)
return tmp_path
@pytest.fixture()
def detroit_vault(vault: Path) -> Path:
_index(vault, "timezone: America/Detroit")
return vault
def _cli(*args: str) -> subprocess.CompletedProcess:
return subprocess.run(
[sys.executable, "-m", "obsidian_wiki.cli", "memory", *args],
capture_output=True, text=True,
)
# --------------------------------------------------------------------------
# pure functions
# --------------------------------------------------------------------------
class TestParse:
def test_empty_and_utc_mean_the_default(self) -> None:
for name in ("", " ", "UTC", "utc", "Z", '"UTC"'):
assert clock.parse_timezone(name) is None, name
def test_an_iana_name_resolves(self) -> None:
assert clock.parse_timezone("America/Detroit") == DETROIT
assert clock.parse_timezone("'Europe/Berlin'") == ZoneInfo("Europe/Berlin")
def test_an_unknown_name_fails_loudly(self) -> None:
with pytest.raises(clock.TimezoneError, match="Mars/Olympus"):
clock.parse_timezone("Mars/Olympus")
assert issubclass(clock.TimezoneError, ValueError)
class TestStamp:
SUMMER = datetime(2026, 10, 10, 18, 40, 37, tzinfo=timezone.utc) # EDT
WINTER = datetime(2026, 11, 2, 18, 40, 37, tzinfo=timezone.utc) # EST
def test_default_is_the_original_z_form(self) -> None:
assert clock.stamp(None, now=self.SUMMER) == "2026-10-10T18:40:37Z"
assert clock.compact(None, now=self.SUMMER) == "20261010T184037Z"
assert clock.today(None, now=self.SUMMER) == "2026-10-10"
def test_a_zone_carries_its_own_offset_for_that_instant(self) -> None:
assert clock.stamp(DETROIT, now=self.SUMMER) == "2026-10-10T14:40:37-04:00"
assert clock.stamp(DETROIT, now=self.WINTER) == "2026-11-02T13:40:37-05:00"
assert clock.compact(DETROIT, now=self.SUMMER) == "20261010T144037-0400"
def test_a_zone_input_is_fine_too(self) -> None:
local = self.SUMMER.astimezone(ZoneInfo("Asia/Tokyo"))
assert clock.stamp(DETROIT, now=local) == "2026-10-10T14:40:37-04:00"
assert clock.stamp(None, now=local) == "2026-10-10T18:40:37Z"
def test_a_bare_date_is_the_calendar_day_in_the_zone(self) -> None:
evening = datetime(2026, 10, 11, 0, 30, 0, tzinfo=timezone.utc) # 20:30 the 10th in Detroit
assert clock.today(None, now=evening) == "2026-10-11"
assert clock.today(DETROIT, now=evening) == "2026-10-10"
def test_live_stamps_have_the_expected_shape(self) -> None:
assert Z_STAMP.match(clock.stamp(None))
assert DETROIT_STAMP.match(clock.stamp(DETROIT))
assert BARE_DATE.match(clock.today(DETROIT))
def test_a_naive_now_is_refused(self) -> None:
with pytest.raises(ValueError, match="aware"):
clock.stamp(DETROIT, now=datetime(2026, 10, 10, 12, 0, 0))
class TestVaultLookup:
def test_no_index_or_no_key_means_default(self, tmp_path: Path) -> None:
assert clock.wiki_timezone(tmp_path) is None
assert clock.timezone_name(tmp_path) == ""
_index(tmp_path)
assert clock.wiki_timezone(tmp_path) is None
assert clock.timezone_name(tmp_path) == ""
def test_the_key_is_read_from_index_frontmatter(self, detroit_vault: Path) -> None:
assert clock.wiki_timezone(detroit_vault) == DETROIT
assert clock.timezone_name(detroit_vault) == "America/Detroit"
def test_quotes_are_tolerated(self, vault: Path) -> None:
_index(vault, 'timezone: "Europe/Berlin"')
assert clock.wiki_timezone(vault) == ZoneInfo("Europe/Berlin")
def test_the_key_in_the_body_does_not_count(self, vault: Path) -> None:
(vault / "index.md").write_text(
"---\ntitle: Wiki Index\n---\n\n# Wiki Index\n\ntimezone: America/Detroit\n", encoding="utf-8"
)
assert clock.wiki_timezone(vault) is None
def test_a_typo_raises(self, vault: Path) -> None:
_index(vault, "timezone: America/Detroi")
with pytest.raises(clock.TimezoneError):
clock.wiki_timezone(vault)
# --------------------------------------------------------------------------
# the writers
# --------------------------------------------------------------------------
class TestWritersWithoutAZone:
"""The regression guard: nothing changes for a vault that never set the key."""
def test_log_hot_adopted_and_tables_stay_utc_z(self, vault: Path) -> None:
line = mem.append_log(vault, "INGEST", {"source": "x"})
assert Z_STAMP.match(mem.parse_log_line(line).timestamp)
hot = mem.rebuild_hot(vault, force=True)
assert Z_STAMP.match(mem.parse_frontmatter(mem.split_frontmatter(hot.text)[0])["updated"])
mem.mark_adopted(vault)
adopted = (vault / mem.ADOPTED_REL).read_text(encoding="utf-8")
assert re.search(r"^adopted: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$", adopted, re.MULTILINE)
todo = mem.add_todo(vault, "Ship the parser")
assert BARE_DATE.match(todo.created) and todo.created == clock.today(None)
fact = mem.set_fact(vault, "stack", "Python")
assert fact.updated == clock.today(None)
assert mem.memory_status(vault)["timezone"] == "UTC"
class TestWritersWithAZone:
def test_every_stamp_carries_the_zone_offset(self, detroit_vault: Path) -> None:
vault = detroit_vault
line = mem.append_log(vault, "INGEST", {"source": "x"})
assert DETROIT_STAMP.match(mem.parse_log_line(line).timestamp), line
hot = mem.rebuild_hot(vault, force=True)
updated = mem.parse_frontmatter(mem.split_frontmatter(hot.text)[0])["updated"]
assert DETROIT_STAMP.match(updated), updated
mem.mark_adopted(vault)
adopted = (vault / mem.ADOPTED_REL).read_text(encoding="utf-8")
assert re.search(r"^adopted: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-0[45]:00$", adopted, re.MULTILINE)
def test_bare_dates_are_the_zone_calendar_day(self, detroit_vault: Path) -> None:
vault = detroit_vault
todo = mem.add_todo(vault, "Ship the parser", origin="projects/x.md")
assert todo.created == clock.today(DETROIT) == todo.touched
closed = mem.set_todo_status(vault, todo.id, "done")
assert closed.touched == clock.today(DETROIT)
fact = mem.set_fact(vault, "stack", "Python")
assert fact.updated == clock.today(DETROIT)
table = (vault / "_meta" / "todos.md").read_text(encoding="utf-8")
assert f"created: {clock.today(DETROIT)}" in table
assert mem.memory_status(vault)["timezone"] == "America/Detroit"
def test_an_explicit_log_timestamp_still_wins(self, detroit_vault: Path) -> None:
line = mem.append_log(detroit_vault, "INGEST", {}, timestamp="2026-01-01T00:00:00Z")
assert line.startswith("- [2026-01-01T00:00:00Z] INGEST")
def test_rebuilding_the_index_keeps_the_key(self, detroit_vault: Path) -> None:
mem.rebuild_index(detroit_vault, force=True)
assert clock.timezone_name(detroit_vault) == "America/Detroit"
text = (detroit_vault / "index.md").read_text(encoding="utf-8")
assert text.count("timezone:") == 1
def test_the_migration_backup_uses_the_zone(self, detroit_vault: Path) -> None:
(detroit_vault / "hot.md").write_text("# Hot\n\n## Key Takeaways\n\nhand-written\n", encoding="utf-8")
result = mem.migrate(detroit_vault)
assert re.search(r"pre-memory-migration-\d{8}T\d{6}-0[45]00$", result["backup_dir"]), result["backup_dir"]
def test_a_typo_fails_the_write(self, vault: Path) -> None:
_index(vault, "timezone: America/Detroi")
with pytest.raises(clock.TimezoneError):
mem.append_log(vault, "INGEST", {})
assert not (vault / "log.md").exists()
# --------------------------------------------------------------------------
# the CLI
# --------------------------------------------------------------------------
class TestCli:
def test_now_prints_the_stamp_to_write(self, detroit_vault: Path) -> None:
result = _cli("now", "--vault", str(detroit_vault))
assert result.returncode == 0, result.stderr
assert DETROIT_STAMP.match(result.stdout.strip()), result.stdout
def test_now_without_a_zone_is_utc_z(self, vault: Path) -> None:
result = _cli("now", "--vault", str(vault))
assert result.returncode == 0, result.stderr
assert Z_STAMP.match(result.stdout.strip()), result.stdout
def test_now_date_and_json(self, detroit_vault: Path) -> None:
date = _cli("now", "--date", "--vault", str(detroit_vault))
assert date.returncode == 0 and date.stdout.strip() == clock.today(DETROIT)
as_json = _cli("now", "--json", "--vault", str(detroit_vault))
assert as_json.returncode == 0, as_json.stderr
import json
payload = json.loads(as_json.stdout)
assert set(payload) == {"timestamp", "date", "timezone"}
assert payload["timezone"] == "America/Detroit"
assert DETROIT_STAMP.match(payload["timestamp"]) and BARE_DATE.match(payload["date"])
def test_status_names_the_zone(self, detroit_vault: Path, vault: Path) -> None:
result = _cli("status", "--vault", str(detroit_vault))
assert result.returncode == 0, result.stderr
assert "timezone: America/Detroit" in result.stdout
def test_a_typo_is_an_error_not_a_utc_stamp(self, vault: Path) -> None:
_index(vault, "timezone: Americas/Detroit")
result = _cli("now", "--vault", str(vault))
assert result.returncode == 1
assert "Americas/Detroit" in result.stderr
assert result.stdout.strip() == ""
def test_sync_stamps_the_log_in_the_zone(self, detroit_vault: Path) -> None:
mem.mark_adopted(detroit_vault)
result = _cli("sync", "INGEST", "source=x", "--vault", str(detroit_vault))
assert result.returncode == 0, result.stderr
log = (detroit_vault / "log.md").read_text(encoding="utf-8")
assert re.search(r"^- \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}-0[45]:00\] INGEST", log, re.MULTILINE), log
# --------------------------------------------------------------------------
# docs
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# setup
# --------------------------------------------------------------------------
class TestSetup:
def test_set_timezone_inserts_replaces_and_validates(self, vault: Path) -> None:
assert clock.set_timezone(vault, "America/Detroit") == "America/Detroit"
text = (vault / "index.md").read_text(encoding="utf-8")
assert text.startswith("---\ntitle: Wiki Index\ntimezone: America/Detroit\n"), text
assert "# Wiki Index" in text and text.count("timezone:") == 1
clock.set_timezone(vault, "Europe/Berlin")
text = (vault / "index.md").read_text(encoding="utf-8")
assert text.count("timezone:") == 1 and "timezone: Europe/Berlin" in text
assert clock.set_timezone(vault, "") == "" and "Europe/Berlin" in (vault / "index.md").read_text()
with pytest.raises(clock.TimezoneError):
clock.set_timezone(vault, "America/Detroi")
assert "Europe/Berlin" in (vault / "index.md").read_text(encoding="utf-8")
def test_set_timezone_on_an_index_without_frontmatter(self, tmp_path: Path) -> None:
(tmp_path / "index.md").write_text("# Wiki Index\n\n## Concepts\n", encoding="utf-8")
clock.set_timezone(tmp_path, "America/Detroit")
text = (tmp_path / "index.md").read_text(encoding="utf-8")
assert text.startswith("---\ntitle: Wiki Index\ntimezone: America/Detroit\n---\n\n# Wiki Index\n"), text
assert clock.wiki_timezone(tmp_path) == DETROIT