Skip to content

feat(core): add deterministic wiki projector - #1381

Open
phernandez wants to merge 1 commit into
mainfrom
codex-1863-wiki-projector-core
Open

feat(core): add deterministic wiki projector#1381
phernandez wants to merge 1 commit into
mainfrom
codex-1863-wiki-projector-core

Conversation

@phernandez

Copy link
Copy Markdown
Member

Why

Basic Memory Cloud Spec 88 requires local Basic Memory and Cloud to project the same accepted project state into byte-equivalent OKF Wiki documents. That deterministic contract belongs in Core so runtime adapters can share one source of truth without adding an agent dependency, queue assumption, or second filesystem.

This is the first Core layer in the Spec 88 stack. It deliberately stops at pure planning; project-partition persistence and Cloud scheduling/ledger integration follow in stacked PRs.

Related Cloud issue: https://github.com/basicmachines-co/basic-memory-cloud/issues/1863

What Changed

  • Added runtime-neutral request, snapshot, source-note, source-change, reserved-document, write, conflict, result, and plan models.
  • Added deterministic planning for root and affected-directory index.md and log.md documents.
  • Added strict materialization deferral, projector self-suppression, checksum-protected writes, byte-identical no-op replay, full rebuild scope expansion, and all-or-nothing reserved-path conflicts.
  • Added user-visible projector result states: current, updating, partial, conflicted, and failed.
  • Added a shared contract fixture pinning exact SHA-256 output for the canonical four-document projection.

Implementation Details

  • src/basic_memory/indexing/wiki_projector.py is pure and performs no I/O. Local and Cloud adapters supply an accepted snapshot and execute the returned canonical write plan through their existing mutation paths.
  • Project-relative paths are normalized and absolute paths fail at the contract boundary.
  • Incremental requests expand affected paths to root and ancestor scopes; import/manual rebuilds cover every note directory.
  • Any unmaterialized accepted position defers all output so indexes and logs never describe a state that storage has not materialized.
  • Any user-claimed reserved path blocks the logical projection rather than publishing mixed-watermark output.
  • Generated index.md and log.md changes from the Wiki Projector are excluded from subsequent projection work and logs.
  • generated.at derives from the accepted source boundary, so replaying identical input at the same watermark produces identical bytes.

Testing

Automated

  • BASIC_MEMORY_ENV=test LOGFIRE_IGNORE_NO_CONFIG=1 uv run pytest -p pytest_mock --no-cov -q tests/indexing/test_wiki_projector.py: 10 passed.
  • uv run ruff format --check src/basic_memory/indexing/wiki_projector.py tests/indexing/test_wiki_projector.py: passed.
  • uv run ruff check src/basic_memory/indexing/wiki_projector.py tests/indexing/test_wiki_projector.py: passed.
  • uv run ty check src/basic_memory/indexing/wiki_projector.py tests/indexing/test_wiki_projector.py: passed.
  • uv run ty check src tests test-int: passed after installing the repository-declared Milvus extra.
  • Load-bearing proof: removing the absolute-path guard made its exact regression fail; restoring it returned the suite to green.
  • Load-bearing proof: bypassing the all-or-nothing reserved-path gate made its exact regression fail; restoring it returned the suite to green.

Manual

  • Inspected deterministic documents and pinned their exact SHA-256 values in tests/fixtures/wiki_projector/basic_projection.json.

Risks / Follow-ups

  • This PR does not persist a project partition head or schedule projector work. Those are intentionally isolated in the next Core/Cloud stack layers.
  • Runtime adapters must execute WikiProjectionWrite values through canonical checksum-protected accepted-note commands; direct storage writes are not compatible with this contract.
  • Cloud will carry the durable run ledger and expose user-visible state. No deployment or merge is part of this PR.

Signed-off-by: phernandez <paul@basicmachines.co>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 30, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-30T01:19:22.352341Z d02f106 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d02f1066ea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if direct_notes:
body.extend(["## Notes", ""])
body.extend(
f"- [[{_without_markdown_suffix(note.path)}|{note.title}]]" for note in direct_notes

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Escape dynamic fields before emitting Markdown

When a note has a valid multiline title such as Bad]]\n- relates_to [[evil, interpolating it directly closes the generated link and creates an additional semantic relation when index.md is parsed; project names and change titles have the same problem in the other rendering paths. Escape or reject Markdown delimiters and line breaks before constructing these canonical generated notes so user metadata cannot change their structure.

AGENTS.md reference: AGENTS.md:L156-L160

Useful? React with 👍 / 👎.

Comment on lines +428 to +430
direct_notes = sorted(
(note for note in notes if _parent_scope(note.path) == scope),
key=lambda note: (note.title.casefold(), note.path.casefold()),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make note ordering total for case-only names

When a case-sensitive project contains Foo.md and foo.md with titles that also compare equally under casefold(), both sort keys are identical and Python preserves whichever snapshot order the adapter supplied. Local and Cloud adapters can therefore produce different index.md bytes and checksums for the same project state; include the original title and path as deterministic tie-breakers.

Useful? React with 👍 / 👎.

Comment on lines +585 to +587
parsed = PurePosixPath(candidate)
if parsed.is_absolute() or any(part in {"", ".", ".."} for part in parsed.parts):
raise ValueError(f"Wiki path must be project-relative and normalized: {path}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject Windows drive-qualified paths

For an input such as C:/outside.md, PurePosixPath reports a relative path with no rejected components, so the planner accepts it and can emit C:/index.md. On Windows, joining that result to a project root switches to the specified drive, meaning an adapter that trusts this contract can fail or write outside the project; validate with PureWindowsPath.drive/is_absolute() rather than relying only on POSIX semantics.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant