Skip to content

Commit 629db95

Browse files
committed
fix: harden semantic dream apply routing
1 parent 0cdbd5c commit 629db95

3 files changed

Lines changed: 165 additions & 4 deletions

File tree

src/dory_core/semantic_write.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,20 @@ def __init__(
8989
resolver_client: OpenRouterClient | None = None,
9090
) -> None:
9191
self.root = Path(root)
92-
self.writer = WriteEngine(root=self.root, index_root=index_root, embedder=embedder)
92+
settings = DorySettings()
93+
self.writer = WriteEngine(
94+
root=self.root,
95+
max_write_bytes=max(settings.max_write_bytes, 256_000),
96+
index_root=index_root,
97+
embedder=embedder,
98+
)
9399
self.registry = EntityRegistry(self.root / ".dory" / "entity-registry.db")
94100
self.claim_store = ClaimStore(self.root / ".dory" / "claim-store.db")
95101
resolved_client = (
96102
resolver_client
97103
if resolver_client is not None
98104
else build_openrouter_client(
99-
DorySettings(),
105+
settings,
100106
purpose="maintenance",
101107
)
102108
)
@@ -670,6 +676,8 @@ def build_semantic_write_plan(
670676
) -> SemanticWritePlan:
671677
resolver = resolver or SubjectResolver(root)
672678
match = resolver.resolve(req.subject, scope=req.scope)
679+
if match is None or _should_create_new_explicit_dream_subject(match, req):
680+
match = _new_subject_match_from_explicit_scope(req)
673681
if match is None:
674682
raise ValueError(f"could not resolve semantic subject: {req.subject}")
675683

@@ -699,6 +707,38 @@ def build_semantic_write_plan(
699707
)
700708

701709

710+
def _new_subject_match_from_explicit_scope(req: MemoryWriteReq) -> SubjectMatch | None:
711+
if req.scope not in {"project", "concept", "decision"}:
712+
return None
713+
slug = normalize_migration_slug(req.subject)
714+
if not slug:
715+
return None
716+
subject_ref = f"{req.scope}:{slug}"
717+
return SubjectMatch(
718+
subject_ref=subject_ref,
719+
family=req.scope,
720+
title=canonical_title_from_subject(subject_ref),
721+
target_path=canonical_target_for_subject(subject_ref),
722+
matched_by="explicit_scope",
723+
confidence="high",
724+
)
725+
726+
727+
def _should_create_new_explicit_dream_subject(match: SubjectMatch, req: MemoryWriteReq) -> bool:
728+
if req.scope not in {"project", "concept", "decision"}:
729+
return False
730+
source = req.source or ""
731+
if "/digests/" not in source and "/inbox/distilled/" not in source:
732+
return False
733+
slug = normalize_migration_slug(req.subject)
734+
if not slug:
735+
return False
736+
requested_subject_ref = f"{req.scope}:{slug}"
737+
if match.subject_ref == requested_subject_ref:
738+
return False
739+
return match.matched_by in {"alias", "llm"}
740+
741+
702742
def _route_target(match: SubjectMatch, req: MemoryWriteReq) -> tuple[str, str, str]:
703743
if match.family == "core":
704744
return match.subject_ref, "core", match.target_path

tests/integration/core/test_semantic_write_flow.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,33 @@ def test_semantic_write_flow_creates_structured_decision_pages(tmp_path: Path) -
132132
assert "## Evidence" in decision.body
133133

134134

135+
def test_semantic_write_engine_allows_large_canonical_rewrites(tmp_path: Path) -> None:
136+
root = tmp_path / "corpus"
137+
(root / "projects" / "dory").mkdir(parents=True)
138+
project_path = root / "projects" / "dory" / "state.md"
139+
project_path.write_text(
140+
"---\ntitle: Dory\naliases: []\n---\n# Dory\n\n## Current State\n\n"
141+
+ ("Existing canonical context.\n" * 700),
142+
encoding="utf-8",
143+
)
144+
145+
engine = SemanticWriteEngine(root)
146+
response = engine.write(
147+
MemoryWriteReq(
148+
action="write",
149+
kind="state",
150+
subject="dory",
151+
content="Dory can update large canonical pages.",
152+
scope="project",
153+
allow_canonical=True,
154+
)
155+
)
156+
157+
assert response.resolved is True
158+
assert response.result == "written"
159+
assert "Dory can update large canonical pages." in project_path.read_text(encoding="utf-8")
160+
161+
135162
def test_semantic_write_dry_run_reports_canonical_target_without_persisting(tmp_path: Path) -> None:
136163
root = tmp_path / "corpus"
137164
(root / "projects" / "dory").mkdir(parents=True)
@@ -193,7 +220,7 @@ def test_semantic_write_dry_run_large_canonical_target_still_reports_route(tmp_p
193220
assert response.target_path == "projects/dory/state.md"
194221
assert response.message is not None
195222
assert response.message.startswith("CANONICAL TARGET projects/dory/state.md")
196-
assert "rendered target exceeds preview write-size limit" in response.message
223+
assert "dry_run: would_replace" in response.message
197224
assert project_path.read_text(encoding="utf-8") == before
198225

199226

tests/unit/test_semantic_write.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from pathlib import Path
44

5-
from dory_core.semantic_write import SubjectResolver, build_semantic_write_plan
5+
from dory_core.semantic_write import SubjectMatch, SubjectResolver, build_semantic_write_plan
66
from dory_core.types import MemoryWriteReq, MemoryWriteResp
77

88

@@ -124,3 +124,97 @@ def test_build_semantic_write_plan_routes_to_canonical_targets(tmp_path: Path) -
124124
assert core_plan.subject_ref == "core:user"
125125
assert core_plan.target_path == "core/user.md"
126126
assert core_plan.resolved_mode == "replace"
127+
128+
129+
def test_build_semantic_write_plan_creates_new_project_from_explicit_scope(tmp_path: Path) -> None:
130+
plan = build_semantic_write_plan(
131+
tmp_path,
132+
MemoryWriteReq(
133+
action="write",
134+
kind="state",
135+
subject="Open Privacy Filter",
136+
content="Open Privacy Filter is active.",
137+
scope="project",
138+
),
139+
)
140+
141+
assert plan.subject_ref == "project:open-privacy-filter"
142+
assert plan.target_subject_ref == "project:open-privacy-filter"
143+
assert plan.target_path == "projects/open-privacy-filter/state.md"
144+
assert plan.matched_by == "explicit_scope"
145+
assert plan.target_exists is False
146+
147+
148+
def test_build_semantic_write_plan_does_not_create_people_from_explicit_scope(tmp_path: Path) -> None:
149+
try:
150+
build_semantic_write_plan(
151+
tmp_path,
152+
MemoryWriteReq(
153+
action="write",
154+
kind="preference",
155+
subject="active",
156+
content="Bad scoped proposal should not create a person.",
157+
scope="person",
158+
),
159+
)
160+
except ValueError as err:
161+
assert "could not resolve semantic subject: active" in str(err)
162+
else:
163+
raise AssertionError("expected unresolved person subject to be rejected")
164+
165+
166+
def test_build_semantic_write_plan_creates_new_dream_project_over_alias_match(tmp_path: Path) -> None:
167+
class _AliasResolver:
168+
def resolve(self, subject: str, *, scope: str | None = None) -> SubjectMatch | None:
169+
return SubjectMatch(
170+
subject_ref="project:privacy-filter-lab",
171+
family="project",
172+
title="Privacy Filter Lab",
173+
target_path="projects/privacy-filter-lab/state.md",
174+
matched_by="alias",
175+
confidence="high",
176+
)
177+
178+
plan = build_semantic_write_plan(
179+
tmp_path,
180+
MemoryWriteReq(
181+
action="write",
182+
kind="state",
183+
subject="open-privacy-filter",
184+
content="Open Privacy Filter is active.",
185+
scope="project",
186+
source="/var/lib/dory/digests/daily/2026-04-23.md",
187+
),
188+
resolver=_AliasResolver(),
189+
)
190+
191+
assert plan.subject_ref == "project:open-privacy-filter"
192+
assert plan.target_path == "projects/open-privacy-filter/state.md"
193+
194+
195+
def test_build_semantic_write_plan_keeps_alias_match_for_non_dream_write(tmp_path: Path) -> None:
196+
class _AliasResolver:
197+
def resolve(self, subject: str, *, scope: str | None = None) -> SubjectMatch | None:
198+
return SubjectMatch(
199+
subject_ref="project:privacy-filter-lab",
200+
family="project",
201+
title="Privacy Filter Lab",
202+
target_path="projects/privacy-filter-lab/state.md",
203+
matched_by="alias",
204+
confidence="high",
205+
)
206+
207+
plan = build_semantic_write_plan(
208+
tmp_path,
209+
MemoryWriteReq(
210+
action="write",
211+
kind="state",
212+
subject="open-privacy-filter",
213+
content="Open Privacy Filter is active.",
214+
scope="project",
215+
),
216+
resolver=_AliasResolver(),
217+
)
218+
219+
assert plan.subject_ref == "project:privacy-filter-lab"
220+
assert plan.target_path == "projects/privacy-filter-lab/state.md"

0 commit comments

Comments
 (0)