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
18 changes: 14 additions & 4 deletions src/attune_author/summaries_polish.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,16 @@ def polish_model() -> str:


def custom_id_for(rel_path: str) -> str:
"""Stable Anthropic ``custom_id`` for one template path."""
return f"sum__{rel_path}"
"""Stable Anthropic ``custom_id`` for one template path.

The Batch API requires ``^[a-zA-Z0-9_-]{1,64}$`` — template paths
(slashes, dots) violate it, so the id is a path hash. The readable
path rides in the request's ``feature`` field, which ``poll_batch``
copies onto each result; ``apply_results`` maps results back
through THAT, never by parsing the id. (Learned from a live 400 on
the first attune-ai run, 2026-07-10.)
"""
return f"sum_{_sha256(rel_path)[:24]}"


def build_requests(help_dir: Path, model: str, *, force: bool = False) -> BuildReport:
Expand Down Expand Up @@ -172,8 +180,10 @@ def build_requests(help_dir: Path, model: str, *, force: bool = False) -> BuildR
)
requests.append(
BatchPolishRequest(
# feature carries the FULL relative path — apply_results
# maps batch results back through it (see custom_id_for).
custom_id=custom_id_for(rel_path),
feature=str(Path(rel_path).parent),
feature=rel_path,
depth=Path(rel_path).stem,
system=_SYSTEM,
user_message=user_message,
Expand Down Expand Up @@ -232,7 +242,7 @@ def apply_results(
stamp = (_now or (lambda: datetime.now(timezone.utc)))().isoformat()
counters = {"applied": 0, "truncated": 0, "errored": 0, "empty": 0}
for result in results:
rel_path = result.custom_id.removeprefix("sum__")
rel_path = result.feature # full relative path — see custom_id_for
if result.error is not None or result.text is None:
counters["errored"] += 1
continue
Expand Down
4 changes: 1 addition & 3 deletions tests/test_anthropic_fable.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ def test_fable_response_leading_thinking_block_skipped() -> None:
is the answer. Hit live 2026-07-10 via the commit regen hook."""
thinking = SimpleNamespace(thinking="reasoning...", type="thinking")
text_block = SimpleNamespace(text="the answer", type="text")
response = SimpleNamespace(
content=[thinking, text_block], stop_reason="end_turn", usage=None
)
response = SimpleNamespace(content=[thinking, text_block], stop_reason="end_turn", usage=None)
client = MagicMock()
client.beta.messages.create.return_value = response
assert (
Expand Down
31 changes: 23 additions & 8 deletions tests/test_summaries_polish.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ def test_builds_one_request_per_entry(self, tmp_path):
report = sp.build_requests(help_dir, "claude-sonnet-5")
assert len(report.requests) == 2
req = report.requests[0]
assert req.custom_id == "sum__cli/concept.md"
assert req.custom_id == sp.custom_id_for("cli/concept.md")
assert req.feature == "cli/concept.md" # apply maps results via this
assert req.model == "claude-sonnet-5"
assert "Current (mechanical) summary: seed" in req.user_message
assert "Body prose here." in req.user_message
Expand Down Expand Up @@ -78,7 +79,7 @@ def test_orphaned_sidecar_key_ignored(self, tmp_path):
sidecar["gone/nope.md"] = "orphan"
(help_dir / "summaries.json").write_text(json.dumps(sidecar))
report = sp.build_requests(help_dir, "claude-sonnet-5")
assert [r.custom_id for r in report.requests] == ["sum__cli/concept.md"]
assert [r.feature for r in report.requests] == ["cli/concept.md"]


class TestModelAndCost:
Expand Down Expand Up @@ -126,8 +127,8 @@ def test_strips_wrapping_quotes(self):
class TestApplyResults:
def _result(self, rel: str, text: str | None, error: str | None = None) -> BatchPolishResult:
return BatchPolishResult(
custom_id=f"sum__{rel}",
feature=str(Path(rel).parent),
custom_id=sp.custom_id_for(rel),
feature=rel,
depth=Path(rel).stem,
text=text,
error=error,
Expand Down Expand Up @@ -286,8 +287,8 @@ def test_source_hash_falls_back_to_body_sha(self, tmp_path):
help_dir,
[
BatchPolishResult(
custom_id="sum__cli/concept.md",
feature="cli",
custom_id=sp.custom_id_for("cli/concept.md"),
feature="cli/concept.md",
depth="concept",
text="Polished.",
error=None,
Expand All @@ -306,8 +307,8 @@ def test_apply_truncates_overlong_result_and_flags_meta(self, tmp_path):
help_dir,
[
BatchPolishResult(
custom_id="sum__cli/concept.md",
feature="cli",
custom_id=sp.custom_id_for("cli/concept.md"),
feature="cli/concept.md",
depth="concept",
text=long_text,
error=None,
Expand Down Expand Up @@ -358,3 +359,17 @@ def fake_poll(batch_id, expected, **kwargs):
assert sidecar["cli/concept.md"] == "Resumed polish."
assert not (help_dir / ".summaries-batch-state.json").exists()
assert "applied 1" in capsys.readouterr().out


class TestCustomIdContract:
def test_matches_batch_api_charset(self):
"""The Batch API enforces ^[a-zA-Z0-9_-]{1,64}$ — a raw template
path (slashes, dots) 400s the whole submit. Hit live 2026-07-10."""
import re

for rel in ("cli/concept.md", "elicitation-forms/troubleshooting.md"):
cid = sp.custom_id_for(rel)
assert re.fullmatch(r"[a-zA-Z0-9_-]{1,64}", cid), cid

def test_distinct_paths_get_distinct_ids(self):
assert sp.custom_id_for("a/b.md") != sp.custom_id_for("a/c.md")
Loading