feat(check): the checker and the contract read a folder's reply
`okf check --payload` takes the reply to one call over a folder as well as a single payload: every bundle's payload is held to all 19 rules on its own, a finding is named with its bundle, one every payload carries alike is reported once, an answer labelled with a bundle its payload does not describe is `answer_misattributed`, and a reply with no answer is `payload_invalid`. No rule is added, and a single payload's report is unchanged. Contract SS 2.5.4 names the folder run and SS 8.11 fixes the reply; the known-positive moves to 24 620 / delta 592. The skill text follows: the working method's steps 1 and 4 name the folder, and the generic skill says to use the server's tools first where they are registered, with the skill as the supplement. The folder is an instruction in both generators, never a path: the bundle's parent written absolute named this checkout, and the test holding generated commands to no repository path fell on it. v1.1 order F, part F4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
570496470b
commit
21f9241712
10 changed files with 240 additions and 29 deletions
|
|
@ -785,14 +785,14 @@ KNOWN_POSITIVE_CASE = "docs/consumption-contract.md, encoded as a JSON string"
|
|||
|
||||
#: `measure()`'s own answer for that file. Vacuous ALONE -- which is why the
|
||||
#: delta below exists.
|
||||
KNOWN_POSITIVE_EXPECTED = 23_672
|
||||
KNOWN_POSITIVE_EXPECTED = 24_620
|
||||
|
||||
#: The second, independent route. `wc -c` reports 23 092 raw bytes for the same
|
||||
#: The second, independent route. `wc -c` reports 24 028 raw bytes for the same
|
||||
#: file; the difference is this file's JSON quoting and escaping overhead. A
|
||||
#: reader can derive it without running `measure()` at all, and it moves the
|
||||
#: moment `measure()` changes what it counts -- which is what stops
|
||||
#: `expected == measured` from proving nothing.
|
||||
KNOWN_POSITIVE_ENCODING_DELTA = 580
|
||||
KNOWN_POSITIVE_ENCODING_DELTA = 592
|
||||
|
||||
#: The two places that file can be, resolved in this order.
|
||||
#:
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@ class Report:
|
|||
#: What the payload says its withheld set holds. `None` when it states no
|
||||
#: total -- unmeasured, never zero.
|
||||
withheld_total: int | None = None
|
||||
#: How many payloads a FOLDER's reply carried (SS 8.11). `None` for a
|
||||
#: single payload, whose report reads exactly as it always has.
|
||||
payloads_examined: int | None = None
|
||||
|
||||
def render(self) -> str:
|
||||
named = (
|
||||
|
|
@ -154,8 +157,9 @@ class Report:
|
|||
if self.withheld_total is None or self.withheld_total == self.withheld_examined
|
||||
else f"{self.withheld_examined} of {self.withheld_total} withheld entries"
|
||||
)
|
||||
over = "" if self.payloads_examined is None else f"{self.payloads_examined} payloads, "
|
||||
denominator = (
|
||||
f"{self.rules_evaluated} rules over {self.excerpts_examined} excerpts and {named}"
|
||||
f"{self.rules_evaluated} rules over {over}{self.excerpts_examined} excerpts and {named}"
|
||||
)
|
||||
if not self.findings:
|
||||
return f"conformant: {denominator}, 0 findings"
|
||||
|
|
@ -853,12 +857,87 @@ def check(skill_text: str, payload: object) -> Report:
|
|||
)
|
||||
|
||||
|
||||
def is_folder_reply(payload: object) -> bool:
|
||||
"""Whether `payload` is the reply to ONE call over a folder of bundles
|
||||
(SS 8.11): `answers`, one per bundle, and no `bundle` of its own."""
|
||||
return isinstance(payload, Mapping) and "answers" in payload and "bundle" not in payload
|
||||
|
||||
|
||||
def check_reply(skill_text: str, reply: object) -> Report:
|
||||
"""`check`, for a single payload or for a folder's reply.
|
||||
|
||||
A folder's reply is not a payload: it is one payload per bundle, and each
|
||||
is held to every rule on its own -- the budget split between them makes
|
||||
none of them a different kind of payload. A finding is named with the
|
||||
bundle whose payload carries it; one that every answer carries
|
||||
identically (a skill's missing section, say) is a fact about the SKILL and
|
||||
is reported once, unnamed. An answer labelled with a bundle its payload
|
||||
does not describe is `answer_misattributed`: the label is what a reader
|
||||
attributes a claim to.
|
||||
"""
|
||||
if not is_folder_reply(reply):
|
||||
return check(skill_text, reply)
|
||||
assert isinstance(reply, Mapping)
|
||||
answers = [_mapping(answer) for answer in _sequence(reply.get("answers"))]
|
||||
if not answers:
|
||||
return Report(
|
||||
findings=(
|
||||
Finding(
|
||||
"payload_invalid",
|
||||
"the folder's reply carries no answer, so there is no payload "
|
||||
"to hold to the contract (SS 8.11)",
|
||||
),
|
||||
),
|
||||
rules_evaluated=len(RULES),
|
||||
excerpts_examined=0,
|
||||
withheld_examined=0,
|
||||
payloads_examined=0,
|
||||
)
|
||||
reports = [check(skill_text, answer.get("payload")) for answer in answers]
|
||||
common = set.intersection(
|
||||
*({(finding.code, finding.message) for finding in report.findings} for report in reports)
|
||||
)
|
||||
findings: list[Finding] = [
|
||||
finding for finding in reports[0].findings if (finding.code, finding.message) in common
|
||||
]
|
||||
for answer, report in zip(answers, reports):
|
||||
label = _text(answer.get("bundle_id"))
|
||||
declared = _text(_mapping(_mapping(answer.get("payload")).get("bundle")).get("bundle_id"))
|
||||
if label != declared:
|
||||
findings.append(
|
||||
Finding(
|
||||
"answer_misattributed",
|
||||
f"an answer is labelled {label!r} and its payload describes "
|
||||
f"{declared!r}; a claim is attributed to the label (SS 8.11)",
|
||||
)
|
||||
)
|
||||
findings.extend(
|
||||
Finding(finding.code, f"[{label}] {finding.message}")
|
||||
for finding in report.findings
|
||||
if (finding.code, finding.message) not in common
|
||||
)
|
||||
totals = [report.withheld_total for report in reports]
|
||||
return Report(
|
||||
findings=tuple(findings),
|
||||
rules_evaluated=len(RULES),
|
||||
excerpts_examined=sum(report.excerpts_examined for report in reports),
|
||||
withheld_examined=sum(report.withheld_examined for report in reports),
|
||||
withheld_total=None if None in totals else sum(t for t in totals if t is not None),
|
||||
payloads_examined=len(reports),
|
||||
)
|
||||
|
||||
|
||||
def parse_args(argv: list[str] | None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
||||
)
|
||||
parser.add_argument("--skill", type=Path, required=True, help="the SKILL.md to check")
|
||||
parser.add_argument("--payload", type=Path, required=True, help="one pre-pass payload (JSON)")
|
||||
parser.add_argument(
|
||||
"--payload",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="one pre-pass payload (JSON), or the reply to one call over a folder of bundles",
|
||||
)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
|
|
@ -877,7 +956,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
except json.JSONDecodeError as exc:
|
||||
print(f"the payload is not readable JSON: {exc}")
|
||||
return 2
|
||||
report = check(skill_text, payload)
|
||||
report = check_reply(skill_text, payload)
|
||||
print(report.render())
|
||||
return 1 if report.findings else 0
|
||||
|
||||
|
|
|
|||
|
|
@ -553,6 +553,11 @@ def _rewrite(
|
|||
# `<BUNDLE_ROOT>` would be the unfilled template's hole inside the one
|
||||
# section that asks for a second run.
|
||||
("<BUNDLE_ROOT>", str(bundle_root)),
|
||||
# The folder form of step 4 (v1.1 F). An instruction, never a path:
|
||||
# the bundle's parent directory is a path the caller never gave, and
|
||||
# written absolute it names a checkout (the test holding generated
|
||||
# commands to "no path into this repository" caught exactly that).
|
||||
("<FOLDER>", GENERIC_FOLDER),
|
||||
]
|
||||
for old, new in replacements:
|
||||
if old not in text:
|
||||
|
|
@ -820,6 +825,11 @@ CARD_COMMAND = "okf card"
|
|||
|
||||
GENERIC_BUNDLE = "<the bundle you were pointed at>"
|
||||
|
||||
#: Step 4's folder, in the generic skill. Lower-case on purpose, like
|
||||
#: `GENERIC_BUNDLE`: it is an instruction to the reader, not a hole a
|
||||
#: generator left.
|
||||
GENERIC_FOLDER = "<the folder that holds the bundles>"
|
||||
|
||||
|
||||
def render_generic() -> str:
|
||||
"""One installable skill for ANY bundle, carrying no bundle's numbers.
|
||||
|
|
@ -844,11 +854,20 @@ def render_generic() -> str:
|
|||
replacements: list[tuple[str, str]] = [
|
||||
(
|
||||
TEMPLATE_HEADER,
|
||||
"**Use the server first.** When an `okf` MCP server is registered — its\n"
|
||||
"tools `okf_describe` and `okf_ask` are then among yours — ask through it: it\n"
|
||||
"is registered once, works from every project and reaches subagents, which\n"
|
||||
"inherit tools and not skills. This skill is the supplement for a session\n"
|
||||
"with no server. It runs the same code over the same bundles, so the two\n"
|
||||
"cannot disagree about an answer, and neither has to be made again when a\n"
|
||||
"bundle is added or rebuilt.\n\n"
|
||||
"**This file is generic: it carries no bundle's identity and no bundle's\n"
|
||||
"numbers,** and it is therefore never stale. It serves whichever bundle you\n"
|
||||
"are pointed at. Before answering, read that bundle's own card:\n\n"
|
||||
"are pointed at — or every bundle under a folder you are pointed at. Before\n"
|
||||
"answering, read the card:\n\n"
|
||||
"```sh\n"
|
||||
f"{CARD_COMMAND} {GENERIC_BUNDLE}\n"
|
||||
f"{CARD_COMMAND} {GENERIC_FOLDER} # every bundle under it, each with its card\n"
|
||||
"```\n\n"
|
||||
"The card is DERIVED from the bundle on every run, never stored in it, so\n"
|
||||
"there is no second artefact that can disagree with the bytes. Its\n"
|
||||
|
|
@ -867,7 +886,9 @@ def render_generic() -> str:
|
|||
" --out /tmp/payload.json\n"
|
||||
"```\n\n"
|
||||
"`--ref` is an **assertion**, never an override: the identity is computed\n"
|
||||
"from the bytes either way, and a mismatch refuses. Read the pre-pass's\n"
|
||||
"from the bytes either way, and a mismatch refuses. It belongs to one\n"
|
||||
"bundle, so leave it out over a folder: each answer there carries its own\n"
|
||||
"bundle's `ref`. Read the pre-pass's\n"
|
||||
"own exit status, which carries three values: **0** a payload was written,\n"
|
||||
"**1** the run happened and refused, **2** the run did not happen at all.",
|
||||
),
|
||||
|
|
@ -948,6 +969,7 @@ def render_generic() -> str:
|
|||
("<KNOWN_POSITIVE_CASE>", okf_consume.KNOWN_POSITIVE_CASE),
|
||||
("<KNOWN_POSITIVE_EXPECTED>", str(okf_consume.KNOWN_POSITIVE_EXPECTED)),
|
||||
("<BUNDLE_ROOT>", GENERIC_BUNDLE),
|
||||
("<FOLDER>", GENERIC_FOLDER),
|
||||
("<PAYLOAD_PATH>", "/tmp/payload.json"),
|
||||
("<SKILL_PATH>", "this file"),
|
||||
("<REF>", "the card's `ref`"),
|
||||
|
|
@ -964,9 +986,11 @@ def render_generic() -> str:
|
|||
description = block_scalar(
|
||||
"Answer one question about ANY OKF bundle from a bounded payload assembled "
|
||||
"by a deterministic pre-pass, marking every claim with its source, its title "
|
||||
"and its provenance locator. Carries no bundle's identity: read the bundle's "
|
||||
f"own card with `{CARD_COMMAND}` first. Use when the user asks a question of, "
|
||||
"or states a hypothesis about, a corpus held as an OKF bundle."
|
||||
"and its provenance locator, over one bundle or every bundle under a folder. "
|
||||
"Carries no bundle's identity: read the card with "
|
||||
f"`{CARD_COMMAND}` first. The supplement to the `okf` MCP server: use its tools "
|
||||
"when they are registered, and this skill when they are not. Use when the user "
|
||||
"asks a question of, or states a hypothesis about, a corpus held as OKF bundles."
|
||||
)
|
||||
header = f"---\nname: {block_scalar(GENERIC_NAME)}\ndescription: {description}\n---\n"
|
||||
return header + text
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue