Phase 5 (ADR-0067 follow-up):
teaching/cross_pack_supersede.py — supersede_cross_pack_chain()
CLI: core teaching supersede ... --cross-pack
--subject-pack-id ... --object-pack-id ...
Strict per-chain residency, anti-leakage, byte-identical rollback
on any post-append re-load failure. 9 new tests.
Articulation benchmark suite (Phase 4 capability proof):
benchmarks/articulation.py — 5 sub-benches
[1] breadth — every intent shape (9 + OOV + cross-pack)
[2] determinism — N reruns / unique-surface count
[3] footprint — psutil RSS profile across T turns
[4] cross-topic — thread context across mixed subjects
[5] ollama-compare — opt-in side-by-side with local Ollama
CLI: core bench --suite articulation
--runs N (det rerun count)
--turns N (footprint sample window)
--ollama-model MODEL --ollama-reruns N
Full operator preamble + JSON report path.
10 new tests cover the bench shape (psutil import-skipped).
Documentation:
benchmarks/README.md — full operator manual: catalogue of every
bench suite, how to read good/neutral/bad results for each sub-
bench, why CORE vs Ollama comparisons are valid on the
determinism axis and not on linguistic quality, workflow guide.
README.md — articulation bench listed in the live-demo grid and
quick-start examples.
Reference run (llama3:8b, 100 turns, 5 reruns):
determinism_all_identical=True
per-turn ΔRSS ≈ 23 KiB
CORE byte_identical_on_every_prompt=True
Ollama unique_surfaces≥2 on every prompt
Verification:
18 new tests pass
Full lane: 2116 passed, 2 skipped, 0 failed in 2:38
206 lines
7.3 KiB
Python
206 lines
7.3 KiB
Python
"""ADR-0067 follow-up — operator-driven supersession of a cross-pack chain.
|
|
|
|
Mirrors :func:`teaching.supersede.supersede_chain` but operates on the
|
|
cross-pack corpus (``teaching/cross_pack_chains/cross_pack_chains_v1.jsonl``).
|
|
|
|
Cross-pack chains carry two pack-residency fields (``subject_pack_id``
|
|
and ``object_pack_id``) that the in-pack ``supersede_chain`` does not
|
|
know about. Rather than overloading that function with optional kwargs
|
|
that change validation behaviour, this module supplies a sibling
|
|
function with the right surface and reuses the same write path
|
|
(``teaching.proposals.append_chain_to_corpus``).
|
|
|
|
Trust boundary (matches ADR-0057):
|
|
|
|
- Append-only: the earlier chain stays on disk; the runtime loader
|
|
honours ``superseded_by`` to drop it from the active view.
|
|
- Single write surface preserved: ``append_chain_to_corpus`` is the
|
|
only function that writes a JSONL line to the corpus.
|
|
- Validation gates run BEFORE the append: review-date format,
|
|
intent whitelist, distinct chain ids, declared pack residency,
|
|
anti-leakage (subject_pack_id != object_pack_id), old chain must
|
|
be active in the current cross-pack index.
|
|
- Post-append re-load confirms the active set shifted as expected;
|
|
any drift rolls back the file bytes.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from chat.cross_pack_grounding import (
|
|
_CORPUS_PATH as _DEFAULT_CROSS_PACK_CORPUS_PATH,
|
|
_all_cross_pack_chains,
|
|
clear_cross_pack_cache,
|
|
)
|
|
from chat.pack_resolver import _pack_lexicon_for
|
|
from teaching.proposals import append_chain_to_corpus
|
|
from teaching.provenance import Provenance
|
|
from teaching.supersede import SupersessionError
|
|
|
|
# Reuse the same intent whitelist as the in-pack path.
|
|
from chat.teaching_grounding import _VALID_INTENTS
|
|
|
|
_REVIEW_DATE_RE: re.Pattern[str] = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
|
|
|
|
def _validate_review_date(value: str) -> str:
|
|
value = (value or "").strip()
|
|
if not _REVIEW_DATE_RE.match(value):
|
|
raise SupersessionError(
|
|
f"review_date must be YYYY-MM-DD; got {value!r}"
|
|
)
|
|
return value
|
|
|
|
|
|
def supersede_cross_pack_chain(
|
|
*,
|
|
old_chain_id: str,
|
|
subject: str,
|
|
intent: str,
|
|
connective: str,
|
|
object_: str,
|
|
subject_pack_id: str,
|
|
object_pack_id: str,
|
|
review_date: str,
|
|
corpus_path: Path | None = None,
|
|
adr_id: str = "adr-0067",
|
|
new_chain_id: str | None = None,
|
|
) -> str:
|
|
"""Retire ``old_chain_id`` in the cross-pack corpus by appending a
|
|
new entry whose ``superseded_by`` references it.
|
|
|
|
Returns the new entry's ``chain_id``. Raises
|
|
:class:`SupersessionError` on any pre-condition violation; the
|
|
corpus is byte-identical on failure.
|
|
|
|
Pre-conditions (cheapest first):
|
|
|
|
1. ``review_date`` matches ``YYYY-MM-DD``.
|
|
2. ``intent`` is in :data:`_VALID_INTENTS`.
|
|
3. All chain fields + both pack ids are non-empty.
|
|
4. ``subject_pack_id != object_pack_id`` (anti-leakage —
|
|
cross-pack chains must actually cross packs).
|
|
5. Declared subject lemma resolves in its named pack; same for
|
|
object.
|
|
6. ``old_chain_id`` is currently active in the cross-pack index.
|
|
7. New chain id is distinct from old and not already active.
|
|
|
|
Post-append:
|
|
|
|
8. Re-load the index; new entry must be active, old must be
|
|
dropped. Any drift → roll back the bytes.
|
|
"""
|
|
path: Path = corpus_path or _DEFAULT_CROSS_PACK_CORPUS_PATH
|
|
|
|
old_id = (old_chain_id or "").strip()
|
|
if not old_id:
|
|
raise SupersessionError("old_chain_id is required")
|
|
|
|
_validate_review_date(review_date)
|
|
|
|
s = (subject or "").strip().lower()
|
|
i = (intent or "").strip().lower()
|
|
c = (connective or "").strip()
|
|
o = (object_ or "").strip().lower()
|
|
sp = (subject_pack_id or "").strip()
|
|
op = (object_pack_id or "").strip()
|
|
if not all((s, i, c, o, sp, op)):
|
|
raise SupersessionError(
|
|
"subject/intent/connective/object and both pack ids are required"
|
|
)
|
|
if i not in _VALID_INTENTS:
|
|
raise SupersessionError(
|
|
f"intent {i!r} is not in the supported whitelist "
|
|
f"({sorted(_VALID_INTENTS)})"
|
|
)
|
|
if sp == op:
|
|
raise SupersessionError(
|
|
"subject_pack_id and object_pack_id must differ — "
|
|
"same-pack entries belong in the in-pack corpus"
|
|
)
|
|
subject_pack = _pack_lexicon_for(sp)
|
|
object_pack = _pack_lexicon_for(op)
|
|
if s not in subject_pack:
|
|
raise SupersessionError(
|
|
f"subject lemma {s!r} not resident in pack {sp!r}"
|
|
)
|
|
if o not in object_pack:
|
|
raise SupersessionError(
|
|
f"object lemma {o!r} not resident in pack {op!r}"
|
|
)
|
|
|
|
# Pre-load index — must include old, must not already include new.
|
|
clear_cross_pack_cache()
|
|
active = {c.chain_id for c in _all_cross_pack_chains()}
|
|
if old_id not in active:
|
|
raise SupersessionError(
|
|
f"old_chain_id {old_id!r} is not active in the cross-pack corpus"
|
|
)
|
|
resolved_new_id = (new_chain_id or "").strip() or f"{i}_{s}_{c}_{o}"
|
|
if resolved_new_id == old_id:
|
|
raise SupersessionError(
|
|
"new chain_id is identical to old_chain_id"
|
|
)
|
|
if resolved_new_id in active:
|
|
raise SupersessionError(
|
|
f"new chain_id {resolved_new_id!r} is already active; "
|
|
"choose a distinct connective/object or pass --new-chain-id"
|
|
)
|
|
|
|
# Compose entry — cross-pack carries the two extra residency fields.
|
|
review_date_clean = review_date.strip()
|
|
provenance = Provenance(
|
|
adr_id=adr_id,
|
|
source="hand_authored",
|
|
review_date=review_date_clean,
|
|
raw=f"{adr_id}:hand_authored:{review_date_clean}:supersede({old_id})",
|
|
)
|
|
|
|
bytes_before = path.read_bytes() if path.exists() else b""
|
|
|
|
# ``append_chain_to_corpus`` doesn't carry the pack-id fields, so
|
|
# we compose our own JSON line directly — staying within the
|
|
# spirit of "one write helper" by reusing the same atomic append
|
|
# pattern + sorted-keys + provenance shape.
|
|
entry = {
|
|
"chain_id": resolved_new_id,
|
|
"subject": s,
|
|
"intent": i,
|
|
"connective": c,
|
|
"object": o,
|
|
"subject_pack_id": sp,
|
|
"object_pack_id": op,
|
|
"domains_subject_k": 2,
|
|
"domains_object_k": 1,
|
|
"provenance": provenance.raw,
|
|
"superseded_by": old_id,
|
|
}
|
|
line = json.dumps(entry, sort_keys=True, separators=(",", ":"))
|
|
with path.open("a", encoding="utf-8") as fh:
|
|
fh.write(line + "\n")
|
|
|
|
# Post-append: re-load + verify active set shifted as expected.
|
|
clear_cross_pack_cache()
|
|
post_active = {c.chain_id for c in _all_cross_pack_chains()}
|
|
if resolved_new_id not in post_active or old_id in post_active:
|
|
# Roll back.
|
|
path.write_bytes(bytes_before)
|
|
clear_cross_pack_cache()
|
|
raise SupersessionError(
|
|
f"post-append re-load rejected the supersession "
|
|
f"(new_active={resolved_new_id in post_active}, "
|
|
f"old_still_active={old_id in post_active}); "
|
|
f"corpus rolled back"
|
|
)
|
|
|
|
# Keep ``append_chain_to_corpus`` reachable from this module's
|
|
# public re-export so callers needing the in-pack write surface
|
|
# can import it from one place when wiring CLI dispatch.
|
|
_ = append_chain_to_corpus
|
|
return resolved_new_id
|
|
|
|
|
|
__all__ = ["supersede_cross_pack_chain"]
|