← Files LegalQuants LitigationARCHIVED FILE
skills/document-discovery/scripts/objection_review.py
19.9 KB · Oct 2, 2026 · 00:33 UTC
#!/usr/bin/env python3
"""Offline review records and exact, version-bound decision handoffs. Stdlib only."""
import argparse
import copy
import hashlib
import json
import re
from pathlib import Path
FORMAT = 1
STATES = {"not_reviewed", "reviewed", "needs_input"}
REUSABLE = {"replacement", "new_variant", "guidance", "new_entry"}
CLASSIFICATIONS = REUSABLE | {
"matter_only",
"substantive_response",
"formatting_only",
"unresolved",
}
FIELD = re.compile(r"\{\{([a-z][a-z0-9_]*)\}\}")
def require(condition, message):
if not condition:
raise ValueError(message)
def digest(value):
raw = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(raw.encode()).hexdigest()
def file_digest(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def unique_pairs(pairs):
result = {}
for key, value in pairs:
require(key not in result, f"Duplicate JSON key: {key}")
result[key] = value
return result
def read(path):
return json.loads(Path(path).read_text(), object_pairs_hook=unique_pairs)
def write(path, value):
"""Never replace a source, an earlier snapshot, or another session's file."""
data = value if isinstance(value, str) else json.dumps(value, indent=2) + "\n"
with Path(path).open("x", encoding="utf-8") as stream:
stream.write(data)
def nonempty(value, label):
require(isinstance(value, str) and bool(value.strip()), f"Missing {label}")
def header(record, kind, version=FORMAT):
require(isinstance(record, dict), "Expected a JSON object")
require(record.get("kind") == kind, f"Expected {kind}")
require(record.get("format_version") == version, "Unsupported format version")
def indexed(items, key="id"):
require(isinstance(items, list), "Expected a list")
result = {}
for item in items:
require(isinstance(item, dict), "Expected an object in list")
nonempty(item.get(key), key)
require(item[key] not in result, f"Duplicate {key}: {item[key]}")
result[item[key]] = item
return result
def entry_check(entry):
"""Check record structure, not whether wording is one coherent legal ground.
Family identifies the semantic objection type; a variant records a material
scope difference. Source excerpts may contain several grounds, but an entry
is reusable wording for one ground. Consolidation and atomicity need review;
neither string similarity nor a valid hash establishes them.
"""
for key in ("id", "family", "label", "variant", "wording", "guidance"):
nonempty(entry.get(key), f"entry {key}")
fields = entry.get("fields")
require(isinstance(fields, dict), "Entry fields must be an object")
require(set(FIELD.findall(entry["wording"])) == set(fields), "Field mismatch")
for key, label in fields.items():
require(FIELD.fullmatch("{{" + key + "}}"), "Invalid field name")
nonempty(label, "field description")
for key in ("conditions", "exclusions", "sources"):
require(isinstance(entry.get(key), list), f"Missing entry {key}")
require(bool(entry["sources"]), "Entry needs source provenance")
def library_check(library):
"""Validate an approved snapshot; empty snapshots are curation starters only.
An approval record and content binding do not establish who approved it.
Existing user-supplied approved snapshots keep their original provenance.
Raw precedent responses must go through pending curation, not be relabeled
approved by the host to satisfy this shape check.
"""
header(library, "objection-library")
nonempty(library.get("id"), "library id")
require(
type(library.get("version")) is int and library["version"] > 0,
"Library version must be a positive integer",
)
for entry in indexed(library.get("entries")).values():
entry_check(entry)
require(entry.get("status") == "approved", "Only approved entries selectable")
approval = entry.get("approval")
require(isinstance(approval, dict), "Approval must be a recorded object")
nonempty(approval.get("record"), "approval record")
return library
def wording(entry, params):
require(isinstance(params, dict), "Substitutions must be an object")
require(set(params) == set(entry["fields"]), "Substitution fields do not match")
for value in params.values():
nonempty(value, "substitution value; use a visible drafting slot if unresolved")
return FIELD.sub(lambda m: params[m[1]], entry["wording"])
def review_check(review):
from objection_source import check_review_source
header(review, "objection-review", review.get("format_version"))
check_review_source(review)
for key in ("id", "title"):
nonempty(review.get(key), key)
source = review.get("source", {})
nonempty(source.get("file"), "source file")
require(
re.fullmatch(r"[a-f0-9]{64}", source.get("sha256", "")),
"Source SHA-256 required",
)
library_check(review["library"])
entries = indexed(review["library"]["entries"])
require(
bool(entries),
"Request review requires a nonempty approved objection library; "
"curate and approve a useful subset first",
)
context = indexed(review.get("context"))
for item in context.values():
for key in ("title", "text", "locator"):
nonempty(item.get(key), f"context {key}")
requests = indexed(review.get("requests"))
require(bool(requests), "No served requests")
for request in requests.values():
for key in ("label", "text", "locator"):
nonempty(request.get(key), f"request {key}")
require(set(request.get("context_ids", [])) <= set(context), "Unknown context")
if "candidate_note" in request:
nonempty(request["candidate_note"], "candidate assessment note")
for s in indexed(request.get("suggestions"), "entry_id").values():
require(s["entry_id"] in entries, "Unknown suggested library entry")
wording(entries[s["entry_id"]], s["params"])
nonempty(s.get("rationale"), "suggestion rationale")
require(isinstance(s.get("basis"), list) and s["basis"], "Basis required")
for item in s["basis"]:
nonempty(item, "candidate basis")
require(isinstance(s.get("missing"), list), "Missing-input list required")
for item in s["missing"]:
require(isinstance(item, str), "Missing inputs must be text")
require(type(s.get("preselected")) is bool, "Preselection must be boolean")
return review
def choice_check(choice, entries, complete=True):
require("entry_id" in choice, "Explicit library origin or null one-off required")
nonempty(choice.get("component_id"), "component id")
require(isinstance(choice.get("wording"), str), "Wording must be text")
if complete:
nonempty(choice["wording"], "selected wording")
require(type(choice.get("edited")) is bool, "Explicit edited flag required")
for key in ("scope", "notes"):
require(isinstance(choice.get(key), str), f"Choice {key} must be text")
entry_id = choice.get("entry_id")
if entry_id is None:
require(
choice.get("edited") and choice.get("params") == {},
"One-off text must be explicitly edited and have no library fields",
)
else:
require(entry_id in entries, "Selected entry absent from original library")
entry = entries[entry_id]
params = choice.get("params")
require(isinstance(params, dict), "Substitutions must be an object")
require(set(params) == set(entry["fields"]), "Substitution fields do not match")
require(
all(isinstance(value, str) for value in params.values()),
"Substitutions must be text",
)
if complete and not choice["edited"]:
for value in params.values():
nonempty(
value,
"substitution value; use a visible drafting slot if unresolved",
)
expected = FIELD.sub(lambda m: params[m[1]], entry["wording"])
require(
choice["edited"] or choice["wording"] == expected,
"Changed library wording must be marked as a request-specific edit",
)
def reviewed_content(review, row):
"""Exact review snapshot, excluding the action that records its acceptance."""
return {
"review_sha256": digest(review),
"request_id": row["request_id"],
"choices": row["choices"],
"notes": row["notes"],
}
def _selection_identity(review, selections, legacy=False):
review_check(review)
require(
selections.get("review_id") == review["id"]
and (
selections.get("review_sha256") == digest(review)
or legacy
and review.get("legacy_review_sha256")
and selections.get("review_sha256") == review["legacy_review_sha256"]
),
"Wrong or changed review",
)
require(
selections.get("source_sha256") == review["source"]["sha256"],
"Wrong served source",
)
require(
selections.get("library_sha256") == digest(review["library"]),
"Wrong library snapshot",
)
require(
selections.get("purpose") in {"progress", "assembly"}, "Unknown export purpose"
)
def selections_check(review, selections, assembly=False):
header(selections, "objection-selections", 2)
_selection_identity(review, selections)
require(
not assembly or selections["purpose"] == "assembly",
"Progress is not an assembly decision export",
)
rows = indexed(selections.get("rows"), "request_id")
require(
list(rows) == [r["id"] for r in review["requests"]],
"Export must retain every request in served order",
)
entries = indexed(review["library"]["entries"])
component_ids = set()
for row in rows.values():
require(row.get("state") in STATES, "Unknown review state")
require(isinstance(row.get("notes"), str), "Row notes must be text")
drafts = indexed(row.get("drafts"), "component_id")
selected_ids = row.get("selected_ids")
require(
isinstance(selected_ids, list), "Ordered selected component IDs required"
)
require(
all(isinstance(key, str) and key in drafts for key in selected_ids)
and len(set(selected_ids)) == len(selected_ids),
"Unknown or repeated selected component",
)
require(
row.get("choices") == [drafts[key] for key in selected_ids],
"Selected wording differs from its saved draft or order",
)
origins = set()
for draft in drafts.values():
choice_check(draft, entries, complete=False)
require(
draft["component_id"] not in component_ids, "Duplicate component id"
)
component_ids.add(draft["component_id"])
if draft["entry_id"]:
require(draft["entry_id"] not in origins, "Repeated library draft")
origins.add(draft["entry_id"])
if row["state"] == "reviewed":
action = row.get("action") or {}
require(
action.get("kind") in {"individual", "batch"},
"Reviewed row requires an explicit review action",
)
nonempty(action.get("at"), "review action timestamp")
require(
action.get("reviewed_content_sha256")
== digest(reviewed_content(review, row)),
"Wording changed after the recorded review; review this request again",
)
for choice in row["choices"]:
choice_check(choice, entries)
else:
require(row.get("action") is None, "Unfinished row cannot carry approval")
return selections
def resume_selections(review, selections):
"""Migrate available legacy edits as drafts without inventing reviewed content."""
if selections.get("format_version") == 2:
return copy.deepcopy(selections_check(review, selections))
header(selections, "objection-selections")
_selection_identity(review, selections, legacy=True)
result = copy.deepcopy(selections)
result.update(format_version=2, purpose="progress", review_sha256=digest(review))
for row in result["rows"]:
row["drafts"] = copy.deepcopy(row["choices"])
row["selected_ids"] = [choice["component_id"] for choice in row["choices"]]
row["legacy_action"] = row.get("action")
row["state"] = (
"needs_input" if row.get("state") == "needs_input" else "not_reviewed"
)
row["action"] = None
return selections_check(review, result)
def materialize(review, selections):
selections_check(review, selections, assembly=True)
rows = indexed(selections["rows"], "request_id")
requests = []
for request in review["requests"]:
row = rows[request["id"]]
choices = row["choices"] if row["state"] == "reviewed" else []
requests.append(
{
"id": request["id"],
"label": request["label"],
"text": request["text"],
"locator": request["locator"],
"state": row["state"],
"components": copy.deepcopy(choices),
"objection_text": "\n\n".join(c["wording"] for c in choices),
"open_review": row["state"] != "reviewed",
"notes": row["notes"],
}
)
return {
"kind": "objection-assembly",
"format_version": FORMAT,
"review_id": review["id"],
"review_sha256": digest(review),
"selections_sha256": digest(selections),
"source": review["source"],
"source_coverage": {
"comparison": copy.deepcopy(review["source_census"]["comparison"]),
"gaps": copy.deepcopy(review["source_census"]["gaps"]),
"capture_limits": copy.deepcopy(
review["source_census"]["extraction"]["limitations"]
),
},
"library": copy.deepcopy(review["library"]),
"requests": requests,
"baseline": None,
}
def render(review):
review_check(review)
assets = Path(__file__).resolve().parents[1] / "assets" / "objection-review"
payload = {
"review": review,
"review_sha256": digest(review),
"library_sha256": digest(review["library"]),
}
# Less-than escaping also prevents closing a script element from source data.
data = json.dumps(payload, ensure_ascii=True).replace("<", "\\u003c")
template = (assets / "review.html").read_text()
return (
template.replace("/* REVIEW_CSS */", (assets / "review.css").read_text())
.replace("/* REVIEW_JS */", (assets / "review.js").read_text())
.replace("REVIEW_DATA", data)
)
def apply_proposals(library, proposals):
library_check(library)
header(proposals, "objection-library-proposals")
nonempty(proposals.get("id"), "proposal batch id")
require(
proposals["id"] not in library.get("applied_batches", []),
"This proposal batch is already applied",
)
require(
proposals.get("base_sha256") == digest(library),
"Library changed; reconcile this proposal against the current snapshot",
)
updated = copy.deepcopy(library)
original_entries = indexed(library["entries"])
entries = indexed(updated["entries"])
changed = set()
accepted = []
for item in indexed(proposals.get("items")).values():
require(item.get("classification") in CLASSIFICATIONS, "Unknown classification")
require(
item.get("decision") in {"pending", "accept", "reject", "defer"},
"Unknown proposal decision",
)
if item["decision"] != "accept":
continue
require(
item["classification"] in REUSABLE, "Non-reusable proposal cannot apply"
)
nonempty(item.get("decision_record"), "Explicit library approval record")
require(bool(item.get("evidence")), "Proposal needs source evidence")
after = copy.deepcopy(item.get("after"))
require(
isinstance(after, dict), "Accepted proposal needs exact resulting entry"
)
entry_check(after)
target = item.get("target_id")
if item["classification"] in {"replacement", "guidance"}:
require(
target in original_entries
and item.get("before") == original_entries[target],
"Proposal before-entry does not match the library",
)
require(after["id"] == target, "Replacement must retain variant identity")
if item["classification"] == "guidance":
require(
after["wording"] == original_entries[target]["wording"],
"Guidance-only update changes wording",
)
else:
require(after["id"] not in entries, "New entry/variant id already exists")
if item["classification"] == "new_variant":
require(
target in original_entries
and item.get("before") == original_entries[target],
"New variant must identify its original entry",
)
require(
after["family"] == original_entries[target]["family"],
"Variant family mismatch",
)
else:
require(
target is None and item.get("before") is None,
"New entry cannot replace an existing entry",
)
require(
after["id"] not in changed, "Conflicting accepted proposals for one entry"
)
changed.add(after["id"])
after["status"] = "approved"
after["approval"] = {
"record": item["decision_record"],
"proposal_batch": proposals["id"],
"proposal_id": item["id"],
}
entries[after["id"]] = after
accepted.append(item["id"])
require(bool(accepted), "No accepted reusable changes; no new version created")
updated["entries"] = list(entries.values())
updated["version"] += 1
updated["parent_sha256"] = digest(library)
updated["applied_batches"] = [*library.get("applied_batches", []), proposals["id"]]
updated["last_decisions"] = {
"batch": proposals["id"],
"accepted": accepted,
"proposals_sha256": digest(proposals),
}
return library_check(updated)
def main():
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest="command", required=True)
for command in ("render", "materialize", "check-selections", "resume"):
cmd = sub.add_parser(command)
cmd.add_argument("--review", required=True)
if command != "render":
cmd.add_argument("--selections", required=True)
if command != "check-selections":
cmd.add_argument("--out", required=True)
cmd = sub.add_parser("apply-library")
cmd.add_argument("--library", required=True)
cmd.add_argument("--proposals", required=True)
cmd.add_argument("--out", required=True)
args = parser.parse_args()
try:
if args.command == "apply-library":
write(args.out, apply_proposals(read(args.library), read(args.proposals)))
else:
review = read(args.review)
if args.command == "render":
write(args.out, render(review))
elif args.command == "materialize":
write(args.out, materialize(review, read(args.selections)))
elif args.command == "resume":
write(args.out, resume_selections(review, read(args.selections)))
else:
selections_check(review, read(args.selections))
print("OK: " + args.command)
except (ValueError, KeyError, TypeError, OSError) as error:
parser.exit(2, f"Not applied: {error}\n")
if __name__ == "__main__":
main()
SHA-256: 60db4095ab4d74e4d2891b9ba3cafe6aea66597f90f13129256720427064292d