Files
GEWU/scripts/build_owner_review_packet.py
T
vasily 78033bb830
CI / quality (push) Canceled after 0s
feat: online verification, node removals, and knowledge browser
Adds claim-bound online evidence records and citation verification scripts,
node removal transport/adapter, retrieval benchmark and owner-review tooling,
GraphCanvas/SourceDetail knowledge browser UI, and the ingest handoff helper.
Bumps the agents/gewu pack to the online-verification branch.
2026-10-16 20:02:29 +08:00

127 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""Render held ingestion drafts and correction proposals as an owner decision sheet."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
def render(items: list[dict[str, Any]]) -> str:
sections = [
"# GEWU owner review sheet",
"",
(
"Mark one decision for each item and add a short rationale. This sheet records review "
"intent only; it does not alter GEWU state or grant acceptance authority."
),
"",
(
"For held drafts: choose keep held, request revision, or initiate the authorized owner "
"challenge workflow. For corrections: choose approve for fresh owner workflow, request "
"revision, or reject. Proposal base commits may be stale; every later action must "
"context and re-evaluate the proposal."
),
"",
]
for index, item in enumerate(items, start=1):
proposal = item.get("proposal", {})
operations = proposal.get("operations", [])
nodes = [operation["node"] for operation in operations if operation.get("node")]
edges = [operation["edge"] for operation in operations if operation.get("edge")]
node = nodes[0] if nodes else {}
is_correction = item.get("source_stage") == "supersession"
kind = "Correction proposal" if is_correction else "Policy-held draft"
proposal_id = proposal.get("proposal_id", "unknown")
revision = proposal.get("revision", "?")
node_id = node.get("node_id", "not available")
node_type = node.get("node_type", "unknown")
sections.extend(
[
f"## {index}. {kind}: {node.get('title', proposal.get('proposal_id', 'Untitled'))}",
"",
f"- Proposal: `{proposal_id}` revision {revision}",
f"- Current state: {item.get('storage_state', 'evaluated; needs-review')}",
f"- Proposal base commit: `{proposal.get('base_commit', 'unknown')}`",
f"- Node: `{node_id}` ({node_type})",
f"- Summary: {node.get('summary', 'No summary recorded.')}",
"",
"### Claims and attached evidence",
"",
]
)
claim_groups = [
("Claim", node.get("claims", [])),
("Assumption", node.get("assumptions", [])),
("Limitation", node.get("limitations", [])),
]
if not any(claims for _, claims in claim_groups):
sections.append("No node claim payload was included in this review item.")
sections.append("")
for group_label, claims in claim_groups:
for claim in claims:
sections.extend(
[
f"- **{group_label} {claim.get('claim_id', 'claim')}:** "
f"{claim.get('text', '')}"
]
)
for evidence in claim.get("evidence", []):
locator = evidence.get("locator", {})
location = ", ".join(
f"{key}={locator[key]}"
for key in ("page", "block_id", "start", "end")
if key in locator
)
quote = evidence.get("quote", "").splitlines()
sections.append(
f" - Evidence `{evidence.get('source_revision_id', '?')}` ({location}):"
)
sections.extend(f" > {line}" for line in quote)
for edge in edges:
sections.append(
f"- Supersession edge: `{edge.get('source', {}).get('id', '?')}` → `"
f"{edge.get('target', {}).get('id', '?')}` "
f"({edge.get('edge_type', 'relationship')})"
)
sections.extend(["", "### Owner decision", ""])
if is_correction:
sections.extend(
[
"- [ ] Approve this replacement for the owner workflow "
"(fresh evaluation still required)",
"- [ ] Request revision",
"- [ ] Reject this correction",
]
)
else:
sections.extend(
[
"- [ ] Keep held; do not create an exception",
"- [ ] Request revision",
"- [ ] Initiate the authorized owner challenge workflow",
]
)
sections.extend(["- Rationale: ", "", "---", ""])
return "\n".join(sections)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", type=Path, required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
items = json.loads(args.input.read_text(encoding="utf-8"))
if not isinstance(items, list) or len(items) != 19:
parser.error("Expected the frozen 19-item held-draft and correction review packet")
output = render(items)
with args.output.open("x", encoding="utf-8") as stream:
stream.write(output)
print(json.dumps({"status": "created", "items": len(items), "output": str(args.output)}))
if __name__ == "__main__":
main()