Complete HOH-WP-0001: closing routine and PQRST record in each seat
CLOSING.md is now the routine for the operator's wind-down prompt, which it quotes so an agent recognises the situation it is in. Linked from README.md beside "How to leave a seat", from the top of ENTRY.md, and from AGENTS.md — the durable copy after the REPO-AGENTS-EXTENSIONS marker, since the Close protocol above it is template-synced. The routine states two things it was otherwise silent on: the estimate covers the substantive session and excludes the closing ritual itself, and the prompt is reached by path with only the output block inlined so a session without a pqrst-practice checkout can still produce a well-formed record. Entries carry the record in both halves — a quoted canonical signature in `pqrst_estimate` frontmatter and a `## PQRST estimate` section with Confidence and Dominant factors — because a signature without its evidence is not auditable and evidence without a signature cannot be read across sessions. ENTRY.md and templates/entry.md updated to match. check-entries.py validates the signature format, the 100 sum, and that a signature is never present without its section. Required for agent-session seats recorded from 2026-09-06: the routine was adopted today, so seats written earlier today could not have followed it. Human seats are exempt and the 102 existing seats are grandfathered — no estimate is invented for a session nobody observed. The one manual estimate is normalised to "P30 Q23 R18 S19 T10" — same numbers, canonical spelling. Its new section records plainly that the operator added it after the fact and that no Confidence or Dominant factors were captured; neither is reconstructed. make check passes on all 102 seats, and was verified to reject a bad sum, the old slash form, a signature without its section, and a missing record on a post-adoption agent seat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ6oF1DtVDKcD1FCpvRVLx Assistant: claude-code Assistant-Model: opus Assistant-Process: 272883@bnt-lap001 Assistant-Session: f40c8f53-fb65-4980-9d29-bcdb3dd946f7
This commit is contained in:
parent
5da5db501c
commit
1ec16e24db
8 changed files with 268 additions and 10 deletions
|
|
@ -21,6 +21,16 @@ REQUIRED_HEADINGS = (
|
|||
"Handoff",
|
||||
)
|
||||
FINISHED = {"handed-forward", "complete"}
|
||||
|
||||
# PQRST records are required on agent seats recorded on or after this date.
|
||||
# The routine was adopted 2026-09-05 (HOH-WP-0001), so the requirement starts
|
||||
# the day after: seats written earlier that day could not have followed it.
|
||||
# Earlier seats are grandfathered: nobody observed those sessions, and inventing an
|
||||
# estimate for one would be exactly the fabricated evidence the practice
|
||||
# forbids. See CLOSING.md and ~/pqrst-practice/spec/PqrstEstimationPractice.md.
|
||||
PQRST_FROM = "2026-09-06"
|
||||
PQRST_HEADING = "PQRST estimate"
|
||||
PQRST_SIG_RE = re.compile(r"^P(\d{1,3}) Q(\d{1,3}) R(\d{1,3}) S(\d{1,3}) T(\d{1,3})$")
|
||||
IMAGE_RE = re.compile(r"!\[[^\]]*\]\(([^)]+)\)")
|
||||
PLACEHOLDER_RE = re.compile(
|
||||
r"^\s*\*?\(No portrait rendered for this entry yet\.?\)\*?\s*$",
|
||||
|
|
@ -50,6 +60,47 @@ def parse_frontmatter(text: str) -> tuple[dict[str, str], str]:
|
|||
return data, body
|
||||
|
||||
|
||||
def check_pqrst(path: Path, fm: dict[str, str], body: str) -> list[str]:
|
||||
"""Validate the PQRST record on a seat, when the seat is required to carry one."""
|
||||
errors: list[str] = []
|
||||
signature = fm.get("pqrst_estimate", "").strip().strip('"').strip("'")
|
||||
has_heading = re.search(rf"^## {re.escape(PQRST_HEADING)}\s*$", body, re.M) is not None
|
||||
|
||||
required = (
|
||||
fm.get("worker_kind", "") == "agent-session"
|
||||
and fm.get("recorded_at", "").strip().strip('"').strip("'") >= PQRST_FROM
|
||||
)
|
||||
|
||||
if required and not signature:
|
||||
errors.append(
|
||||
f"{path.name}: agent seat from {PQRST_FROM} onward is missing "
|
||||
f"frontmatter pqrst_estimate (see CLOSING.md)"
|
||||
)
|
||||
if required and not has_heading:
|
||||
errors.append(f"{path.name}: missing heading ## {PQRST_HEADING}")
|
||||
|
||||
if signature:
|
||||
match = PQRST_SIG_RE.match(signature)
|
||||
if match is None:
|
||||
errors.append(
|
||||
f"{path.name}: pqrst_estimate must be a canonical signature "
|
||||
f'like "P30 Q23 R18 S19 T10", got {signature!r}'
|
||||
)
|
||||
else:
|
||||
total = sum(int(g) for g in match.groups())
|
||||
if total != 100:
|
||||
errors.append(
|
||||
f"{path.name}: pqrst_estimate must sum to 100, got {total} ({signature})"
|
||||
)
|
||||
if not has_heading:
|
||||
errors.append(
|
||||
f"{path.name}: has pqrst_estimate but no ## {PQRST_HEADING} section — "
|
||||
f"a signature without its dominant factors is not auditable"
|
||||
)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def check_entry(path: Path, ids: dict[str, Path]) -> list[str]:
|
||||
errors: list[str] = []
|
||||
text = path.read_text(encoding="utf-8")
|
||||
|
|
@ -70,6 +121,8 @@ def check_entry(path: Path, ids: dict[str, Path]) -> list[str]:
|
|||
if re.search(rf"^## {re.escape(heading)}\s*$", body, re.M) is None:
|
||||
errors.append(f"{path.name}: missing heading ## {heading}")
|
||||
|
||||
errors.extend(check_pqrst(path, fm, body))
|
||||
|
||||
if PLACEHOLDER_RE.search(text):
|
||||
errors.append(f"{path.name}: still has a 'No portrait rendered' placeholder")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue