P19 DEL A made a direction name the requirement that binds it; P20/A1 made the reply carry the DOCUMENT's own title and number instead of echoing the caller's arguments. Re-measured at the head of this session against the six round-5 debate traces: requirement_hit is 0 of 20 approach rows and 0 of 12 declarations -- the third round in a row at zero. P21/C1 made the runs LOOK first and it worked on its own terms (distinct documents before a declaration went 1,1,1,2,5,13 -> 3,3,5,7,11,12) and the hit did not move. The runs were made to read MORE, not righter. The reply now compares: it names the directions the run was commissioned to pursue and says which of their words appear in the declared document's own title and number, or that none do. A REPORT, never a gate -- the declaration is recorded either way, because a requirement can bind a measure without sharing a word with the name someone gave it, which is exactly how the alternative rule the C1 measurement rejected failed one rung over. The words compared are the DOCUMENT's, never `ref`: a comparison against the caller's own argument can only ever agree. Matching is generous in both directions, and that failure direction is chosen -- a false "no overlap" pushes a model away from a declaration that was right, a false "overlap" merely keeps the report quiet. MEASURED BEFORE IT WAS BUILT, offline against the six traces as the order required (no paid calls in DEL B): the rule speaks on 10 of 12 declarations and stays quiet on 2. A rule that spoke on 12 of 12, or on 0 of 12, could not tell the two classes apart. `labels` defaults to empty, so every call site written before today is byte-identical and the three keys are ABSENT rather than empty -- "there was nothing to compare against" and "we compared and found nothing" are different facts. RUN-level, as the declaration itself is (P19 A4). Also re-measured: the order cited requirement_hit as "0 of 12". The field is per APPROACH (0 of 20); 12 is the number of DECLARATIONS (7 distinct, 0 hits). Both zero, so the conclusion stands, but they are two populations. Load-bearing MEASURED (tests/test_requirement_comparison_loadbearing.py, 8 arms), eight mutations all red against the WHOLE suite + green control 1881/5 (from 1873/5, superset, 0 removed) and golden demo-transcript.stdout BYTE-UNCHANGED (shasum -a 1 of the CONTENT = ea8c534773acdbe41ae68f2c55724d69aaf8be4f): B1 detach the run.py wiring (1 red, that arm alone) - B2 always report an overlap (5) - B3 never report one (2) - B4 compare against the caller's ref (1, that arm alone) - B5 make it a gate (5) - B6 emit the keys with no directions (2, one an OLDER independent witness) - B7 exact token equality instead of substring (1) - B8 drop the minimum word length (3). Honesty limits, stated: no LIVE model has read the comparison yet (DEL D is the measurement); the report cannot say a requirement IS right, only that it shares no word with the direction; and finding 4 (`named` 1/20) is this same matter from the other side, so DEL D measures it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
226 lines
10 KiB
Python
226 lines
10 KiB
Python
"""P22 DEL B — ``declare_requirement`` answers with a COMPARISON, not a confirmation.
|
|
|
|
MEASURED (P21 funn 2, re-measured at the head of okt 126 against the six round-5 debate traces):
|
|
``requirement_hit`` is **0 of 20** approach rows and **0 of 12** declarations — the third round in
|
|
a row at zero. P21/C1 made the runs LOOK first, and it worked on its own terms: distinct documents
|
|
opened before a declaration went from 1,1,1,2,5,13 to 3,3,5,7,11,12. The hit did not move. The
|
|
runs were made to read more, not righter.
|
|
|
|
P20/A1 had already made the reply carry the document's OWN title and number instead of echoing the
|
|
caller's arguments. What nobody said was whether that document has anything to do with the
|
|
direction the run is committed to. This turns the reply into that comparison.
|
|
|
|
**A REPORT, never a gate.** The declaration is recorded either way, and arm (c) is what holds that
|
|
open. A gate on word overlap would refuse legitimate declarations — a requirement can bind a
|
|
measure without sharing a word with the name someone gave it — which is precisely how the
|
|
alternative rule P21/C1 measured and rejected failed, one rung over.
|
|
|
|
**Measured before it was built**, offline against the six traces: the rule speaks on **10 of 12**
|
|
declarations and stays quiet on 2 (both fv412, on ``materialer``). A rule that spoke on 12 of 12,
|
|
or on 0 of 12, could not tell the two classes apart.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from portfolio_optimiser.simulation import scripted_factory
|
|
|
|
from portfolio_optimiser import okf
|
|
from portfolio_optimiser.explore import ToolCall, navigator_tools
|
|
from portfolio_optimiser.mandate import Approach, Mandate
|
|
from portfolio_optimiser.run import run_project
|
|
|
|
_EXAMPLES = Path(__file__).resolve().parents[1] / "shared" / "examples"
|
|
_TUNNEL = _EXAMPLES / "tunnel-hauglia"
|
|
_BASE_ID = "tunnel-hauglia"
|
|
|
|
#: The fixture document the arms declare. Its title is ASCII-clean, which is what lets a label
|
|
#: share a word with it without a marker carrying a multibyte character into a scripted run.
|
|
_DOC = "tiltak-portalskjerming.md"
|
|
|
|
#: A direction whose words are IN that title ("Portalskjerming: senke L20 ...").
|
|
_MATCHING = "Billigere portalskjerming"
|
|
#: A direction that shares nothing with it. Checked against the document's own tokens, both ways.
|
|
_FOREIGN = "Asfaltdekke gjenbruk"
|
|
|
|
|
|
def _wired(labels: tuple[str, ...]) -> tuple[dict[str, Any], list[ToolCall], list[Any]]:
|
|
opened: list[ToolCall] = []
|
|
declared: list[Any] = []
|
|
tools = navigator_tools((str(_TUNNEL),), opened=opened, requirements=declared, labels=labels)
|
|
return {t.name: t for t in tools}, opened, declared
|
|
|
|
|
|
def _declare(
|
|
labels: tuple[str, ...], *, ref: str = "Krav 1.1-1"
|
|
) -> tuple[dict[str, Any], list[Any]]:
|
|
tools, opened, declared = _wired(labels)
|
|
for name in [f.name for f in okf.navigate_bundle(str(_TUNNEL)).context_files][:3]:
|
|
opened.append(ToolCall(name="read_file", bundle_id=_BASE_ID, path=name))
|
|
opened.append(ToolCall(name="read_file", bundle_id=_BASE_ID, path=_DOC))
|
|
answer = tools["declare_requirement"].func(bundle_id=_BASE_ID, path=_DOC, ref=ref)
|
|
return answer, declared
|
|
|
|
|
|
# --- (a)/(b) the discriminator: SAME document, SAME ref, different directions ---------------------
|
|
|
|
|
|
def test_a_direction_that_shares_a_word_is_told_which_one() -> None:
|
|
"""KNOWN-POSITIVE. The document's title carries the direction's own word, and the reply says
|
|
so — the half that keeps the report from being one that only ever complains."""
|
|
answer, _ = _declare((_MATCHING,))
|
|
assert answer["overlap"] == ["portalskjerming"], answer["overlap"]
|
|
assert "portalskjerming" in answer["compare"]
|
|
|
|
|
|
def test_a_direction_that_shares_nothing_is_told_that_too() -> None:
|
|
"""KNOWN-NEGATIVE, and the measured case: the SAME document and the SAME ``ref`` as the arm
|
|
above — the only difference is the direction. That is what makes this a comparison rather than
|
|
a confirmation, and it is exactly the 10-of-12 class the round-5 traces fall into."""
|
|
answer, _ = _declare((_FOREIGN,))
|
|
assert answer["overlap"] == []
|
|
assert "No word of any of them appears" in answer["compare"], answer["compare"]
|
|
assert repr(_FOREIGN) in answer["compare"], answer["compare"]
|
|
|
|
|
|
# --- (c) a REPORT, never a gate ------------------------------------------------------------------
|
|
|
|
|
|
def test_a_declaration_with_no_overlap_is_still_recorded() -> None:
|
|
"""The whole difference between this rung and P21/C1's. A requirement can bind a measure
|
|
without sharing a word with the name someone gave it, so refusing here would refuse
|
|
legitimate declarations — the failure of the alternative rule the C1 measurement rejected."""
|
|
answer, declared = _declare((_FOREIGN,))
|
|
assert answer["declared"] is True
|
|
assert len(declared) == 1, "a reported mismatch must not swallow the declaration"
|
|
|
|
|
|
# --- (d) it reads the DOCUMENT, never the caller's own argument ----------------------------------
|
|
|
|
|
|
def test_the_comparison_never_reads_the_callers_own_ref() -> None:
|
|
"""``ref`` is the caller's argument echoed back, and a comparison against the caller's own
|
|
input can only ever agree — P20/A1's rule (read off the base, never off the arguments)
|
|
applied to the half P20 did not reach. A ``ref`` stuffed with the direction's words must not
|
|
manufacture an overlap."""
|
|
answer, _ = _declare((_FOREIGN,), ref="Asfaltdekke gjenbruk krav")
|
|
assert answer["overlap"] == [], answer["overlap"]
|
|
|
|
|
|
# --- (e) no directions -> the P20 reply, unchanged ------------------------------------------------
|
|
|
|
|
|
def test_without_directions_the_reply_is_the_one_p20_shipped() -> None:
|
|
"""The exploration mints its own directions, so at declaration time it HAS none — and every
|
|
call site that passes no labels must be byte-identical to before. Absent keys, not empty ones:
|
|
"there was nothing to compare against" and "we compared and found nothing" are different
|
|
facts, and only one of them is true here."""
|
|
answer, _ = _declare(())
|
|
assert set(answer) == {
|
|
"declared",
|
|
"bundle_id",
|
|
"path",
|
|
"ref",
|
|
"title",
|
|
"req_number",
|
|
"binds",
|
|
}, sorted(answer)
|
|
|
|
|
|
# --- (f)/(g) the matching rule's two measured properties -----------------------------------------
|
|
|
|
|
|
def test_matching_is_generous_in_both_directions() -> None:
|
|
"""The failure direction chosen on purpose. A direction whose word is a PREFIX of the
|
|
document's own word counts, and so does the reverse — Norwegian inflects ("rundkjoring" vs
|
|
"Rundkjoringer") and a strict rule would report "no overlap" on a declaration that was right,
|
|
which is the only one of the two errors that can push a model away from a correct answer."""
|
|
# label word LONGER than the document's own ("portalskjerming" is a prefix of it)
|
|
assert _declare(("Portalskjermingen paa nordsiden",))[0]["overlap"] == ["portalskjermingen"]
|
|
# and SHORTER: the document says "senke", the direction "senkekostnader"
|
|
assert _declare(("Senkekostnader",))[0]["overlap"] == ["senkekostnader"]
|
|
|
|
|
|
def test_short_words_cannot_manufacture_an_overlap() -> None:
|
|
"""Below the minimum every label shares "for", "med", "til" with half a corpus, and the report
|
|
would speak of an overlap nobody meant. The control is the arm above: the same rule that
|
|
rejects these still finds the real word."""
|
|
answer, _ = _declare(("Ny og med til for",))
|
|
assert answer["overlap"] == [], answer["overlap"]
|
|
|
|
|
|
# --- (h) the commission's labels actually REACH the tool in a run --------------------------------
|
|
|
|
_PID = "TUNNEL-HAUGLIA"
|
|
_VERDICT_INPUT = {"decision": "approved", "rationale": "expert reviewed (test)"}
|
|
_VALID_REPLY = (
|
|
'{"measure":"LED-retrofit","affected_items":'
|
|
'[{"code":"ENERGI-TOTAL-EL","quantity":300000,"unit_cost":1.0}],'
|
|
'"claimed_saving_nok":30000}'
|
|
)
|
|
_CHECKER_REPLY = "Reasoning holds.\nVERDICT: APPROVE"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_a_commissioned_run_reaches_the_tool_with_its_own_directions() -> None:
|
|
"""The WIRING, measured behaviourally rather than by reading ``run.py``. A source assertion is
|
|
a lint; this drives the real debate with a step manuscript that declares, and reads the
|
|
comparison back out of the tool's own answer. Without ``labels=`` in ``run.py`` the reply
|
|
carries no ``compare`` at all and this arm falls."""
|
|
concepts = [f.name for f in okf.navigate_bundle(str(_TUNNEL)).context_files]
|
|
script = {
|
|
"proposer": [
|
|
*(
|
|
{"call": "read_file", "args": {"bundle_id": _BASE_ID, "path": name}}
|
|
for name in concepts[:3]
|
|
),
|
|
{"call": "read_file", "args": {"bundle_id": _BASE_ID, "path": _DOC}},
|
|
{
|
|
"call": "declare_requirement",
|
|
"args": {"bundle_id": _BASE_ID, "path": _DOC, "ref": "Krav 1.1-1"},
|
|
},
|
|
_VALID_REPLY,
|
|
_VALID_REPLY,
|
|
_VALID_REPLY,
|
|
_VALID_REPLY,
|
|
],
|
|
"checker": _CHECKER_REPLY,
|
|
}
|
|
seen: list[str] = []
|
|
|
|
def factory(role: str) -> Any:
|
|
client = scripted_factory(script, [])(role)
|
|
original = client._inner_get_response
|
|
|
|
def recording(*, messages, options, stream=False, **kwargs): # type: ignore[no-untyped-def]
|
|
for message in messages:
|
|
for content in getattr(message, "contents", ()) or ():
|
|
value = getattr(content, "result", None)
|
|
if value:
|
|
seen.append(str(value))
|
|
return original(messages=messages, options=options, stream=stream, **kwargs)
|
|
|
|
client._inner_get_response = recording
|
|
return client
|
|
|
|
await run_project(
|
|
_PID,
|
|
"local",
|
|
docs_dir=str(_TUNNEL),
|
|
bundle_dir=str(_TUNNEL),
|
|
verdict_input=_VERDICT_INPUT,
|
|
client_factory=factory,
|
|
mandate=Mandate(
|
|
objective="Find savings",
|
|
approaches=(Approach(id="a1", label=_FOREIGN, description="expert's reason"),),
|
|
),
|
|
)
|
|
|
|
blob = "\n".join(seen)
|
|
assert "No word of any of them appears" in blob, (
|
|
"the commission's directions never reached the declaration rung: " + blob[-600:]
|
|
)
|
|
assert _FOREIGN in blob
|