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
8 changes: 7 additions & 1 deletion src/vouch/provenance/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from collections.abc import Iterable

from .. import audit
from ..models import ProposalKind, ProposalStatus
from ..models import PageStatus, ProposalKind, ProposalStatus
from ..storage import ArtifactNotFoundError, KBStore
from .model import Edge, EdgeKind, NodeKind, sort_edges

Expand Down Expand Up @@ -221,6 +221,12 @@ def add(
add(c.id, eid, EdgeKind.APPROVED_BY, ts, sess)

for p in store.list_pages():
# Archived pages are intentional retirements. Leaving them in the
# graph undoes archive for anything that renders it, and disagrees
# with recall / digest / search / neighbors, which all read the same
# live set.
if p.status is PageStatus.ARCHIVED:
continue
node_kinds[p.id] = NodeKind.PAGE
p_ts = p.updated_at.isoformat()
for cid in p.claims:
Expand Down
46 changes: 46 additions & 0 deletions tests/test_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,49 @@ def test_kb_why_missing_param_over_jsonl(store: KBStore) -> None:
resp = handle_request({"id": "4", "method": "kb.why", "params": {}})
assert resp["ok"] is False
assert resp["error"]["code"] == "missing_param"


# --- archived pages are out of the live set (#701) --------------------------


def test_archived_pages_contribute_no_nodes_or_edges(store: KBStore) -> None:
"""Regression for #701: `build_graph` walked every page with no lifecycle
filter, so a retired topic stayed in the provenance graph — undoing
archive for anything that renders it."""
from vouch.provenance.graph import build_graph

_seed(store)
store.put_page(Page(id="page-dead", title="Dead", type=PageType.CONCEPT,
claims=["c-new"], status=PageStatus.ARCHIVED))

graph = build_graph(store)
assert not [e for e in graph.edges if e.src_id == "page-dead"]
assert "page-dead" not in graph.nodes()


def test_live_and_draft_pages_still_embed(store: KBStore) -> None:
"""The filter is archive-only: a draft page is unreviewed, not retired,
and the seed's two active pages must keep their EMBEDS edges."""
from vouch.provenance.graph import build_graph

_seed(store)
embedders = {
e.src_id for e in build_graph(store).edges if e.kind.value == "embeds"
}
assert {"page-alpha", "page-beta", "page-draft"} <= embedders


def test_archiving_a_page_removes_it_from_impact(store: KBStore) -> None:
"""The user-visible consequence: `impact` stops naming a page the wiki
no longer carries."""
_seed(store)
before = prov.impact(store, claim_id="c-new", depth=2)
assert "page-alpha" in str(before["dependents"])

page = store.get_page("page-alpha")
page.status = PageStatus.ARCHIVED
store.update_page(page)

after = prov.impact(store, claim_id="c-new", depth=2, use_cache=False)
assert "page-alpha" not in str(after["dependents"])
assert "page-beta" in str(after["dependents"])
Loading