Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Effect classes used below:
| Read exact material/resources | CLI `read` / `resource`; Python `KnowledgeReader.read` / `KnowledgeReader.resource` | Read-only | Returns the requested revision or an explicit unavailable/stale error; never substitutes newer bytes |
| Export legacy cited material | CLI `context` | Read-only | Cited package export for manual use with a coding agent; not a conversation store and not a full Collect equivalent |
| Create/update a store candidate | Python `KnowledgeStore.create` / `KnowledgeStore.update` | Candidate-writing | One explicitly authorized local store; expected-revision updates with reason and evidence; retained history; no promotion and no edits to package/checkout content |
| Inspect a store candidate's history | Python `open_knowledge_store(...).history` | Read-only | Opens an existing store and reads the revision lineage for one store ID |
| Amend repository-owned knowledge | Scoped repository PR | — | Repository review and integration authority apply; this is not a runtime store Update |
| Inspect/prepare experiments | CLI `doctor`; `plan` or `--dry-run` | Read-only inspection / preview | `doctor` may invoke bounded probes and does not certify target compatibility; `plan` previews a specification without persisting a campaign or authorizing execution |
| Execute/inspect experiments | CLI `run`; `status`; `report` | Experiment-executing / read-only / report-generating | `run` executes caller-supplied commands and records evidence; `status` reads recorded progress; `report` generates HTML from recorded evidence |
Expand Down Expand Up @@ -194,6 +195,11 @@ for scoring, provenance and accounting. For example:

## Store candidates (Python)

Open the store with `inferrsi.apps.knowledge.open_knowledge_store`. Pass the
authorized `store_root` and the store's reserved `skill_root`, if configured.
The default opens an existing store read-only for history. Pass
`writable=True` explicitly to create or update candidates.

### Create / Update — `KnowledgeStore.create` / `.update`

- **Inputs:** a complete candidate `Bundle`; Update additionally requires the expected current revision, a reason and an explicit evidence list.
Expand Down
11 changes: 10 additions & 1 deletion src/inferrsi/apps/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@
from inferrsi.knowledge.knowledge_store import KnowledgeStore


def open_knowledge_store(
*, store_root: Path, skill_root: Path | None = None, writable: bool = False,
) -> KnowledgeStore:
"""Open one caller-authorized local store for history or an explicit write."""
if type(writable) is not bool:
raise ValueError("writable must be a boolean")
return KnowledgeStore(store_root, skill_root=skill_root, read_only=not writable)


def knowledge_reader(
*, skill_root: Path | None = None, store_root: Path | None = None,
) -> KnowledgeReader:
"""Select a Knowledge reader without enabling store writes."""
store = (
KnowledgeStore(store_root, skill_root=skill_root, read_only=True)
open_knowledge_store(store_root=store_root, skill_root=skill_root)
if store_root is not None
else None
)
Expand Down
44 changes: 42 additions & 2 deletions tests/test_apps_knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

import pytest

from inferrsi.apps.knowledge import knowledge_reader, read
from inferrsi.knowledge.knowledge_operations import RevisionConflict
from inferrsi.apps.knowledge import knowledge_reader, open_knowledge_store, read
from inferrsi.knowledge.knowledge_operations import Bundle, RevisionConflict
from inferrsi.knowledge.knowledge_store import KnowledgeStore

_IDENTITY = "inferrsi.common.skills.audit_h3_runtime_residency"
Expand Down Expand Up @@ -51,3 +51,43 @@ def count_owners():
assert revision == view["revision"]
assert view["content"].endswith("Example\n")
assert len(scans) == 1


def test_apps_store_requires_explicit_write_and_retains_fixed_history(tmp_path):
root = tmp_path / "candidate-store"
root.mkdir()
with pytest.raises(ValueError, match="requires an existing database"):
open_knowledge_store(store_root=root)
with pytest.raises(ValueError, match="boolean"):
open_knowledge_store(store_root=root, writable="yes")
assert not list(root.iterdir())

source = knowledge_reader().read(_IDENTITY)
source_ref = f"package:{_IDENTITY}"
identity = "apps-example-candidate"
metadata = {
**source.metadata,
"id": identity,
"state": "candidate",
"derived_from": [{"ref": source_ref, "revision": source.revision}],
}
bundle = Bundle(metadata, source.content, source.dependencies)
store = open_knowledge_store(store_root=root, writable=True)
first = store.create(bundle)
updated = Bundle(
{**metadata, "title": "Revised candidate"}, bundle.content, bundle.dependencies,
)
second = store.update(updated, expected=first, reason="clarify title", evidence=[])
with pytest.raises(RevisionConflict, match="Stale"):
store.update(bundle, expected=first, reason="stale write", evidence=[])

viewer = open_knowledge_store(store_root=root)
assert viewer.history(identity) == [
{"revision": first, "parent": None, "reason": "create", "evidence": []},
{"revision": second, "parent": first, "reason": "clarify title", "evidence": []},
]
with pytest.raises(ValueError, match="read-only"):
viewer.update(bundle, expected=second, reason="read-only write", evidence=[])
reader = knowledge_reader(store_root=root)
assert read(reader, f"store:{identity}")[1] == second
assert read(reader, f"store:{identity}", revision=first)[1] == first
Loading