← Files LegalQuants TransactionalARCHIVED FILE

skills/definition-check/scripts/context_query.py

9.05 KB · Oct 3, 2026 · 06:34 UTC

↓ Download file

#!/usr/bin/env python3
"""Execute one bounded context request against a DOCX for an agent worker."""

from __future__ import annotations

import argparse
import json
import os
import re
import sys
import tempfile
from dataclasses import asdict
from pathlib import Path
from typing import Optional

from _runtime_gate import require_supported_python

require_supported_python()

from definition_check.context import RetrievalBudget, RetrievalState, retrieve
from definition_check.matter import qualify_location
from definition_check.models import ContextRequest, Location
from definition_check.ooxml import IntakeError, extract_docx
from definition_check.workspace import (
    WorkspaceError,
    ensure_workspace,
    require_within_workspace,
)

_MAX_REQUEST_BYTES = 64 * 1024
CONTEXT_REQUEST_SCHEMA_VERSION = "context-request-v1"
CONTEXT_RESULT_SCHEMA_VERSION = "context-result-v1"


def _read_object(path: Path) -> dict:
    if path.stat().st_size > _MAX_REQUEST_BYTES:
        raise ValueError(f"request file exceeds {_MAX_REQUEST_BYTES} bytes")
    value = json.loads(path.read_text(encoding="utf-8"))
    if not isinstance(value, dict):
        raise ValueError("request file must contain one JSON object")
    return value


def _request(value: dict) -> ContextRequest:
    allowed = {
        "schema_version",
        "id",
        "candidate_id",
        "request_type",
        "query",
        "reason",
        "requested_by",
        "status",
        "hop",
        "max_results",
        "result_evidence_ids",
    }
    extra = set(value) - allowed
    if extra:
        raise ValueError(f"unsupported request fields: {sorted(extra)}")
    if value.get("schema_version") != CONTEXT_REQUEST_SCHEMA_VERSION:
        raise ValueError("context request has an unsupported schema_version")
    for field in ("id", "candidate_id", "query", "reason", "requested_by"):
        if not isinstance(value.get(field), str) or not value[field].strip():
            raise ValueError(f"context request {field} must be a non-empty string")
    results = value.get("result_evidence_ids", [])
    if not isinstance(results, list) or any(
        not isinstance(item, str) for item in results
    ):
        raise ValueError("result_evidence_ids must be an array of strings")
    return ContextRequest(
        id=str(value["id"]),
        candidate_id=str(value["candidate_id"]),
        request_type=str(value["request_type"]),
        query=str(value["query"]),
        reason=str(value["reason"]),
        requested_by=str(value["requested_by"]),
        status=str(value.get("status", "requested")),
        hop=int(value.get("hop", 1)),
        max_results=int(value.get("max_results", 20)),
        result_evidence_ids=tuple(results),
    )


def _location(value: Optional[dict]) -> Optional[Location]:
    if value is None:
        return None
    allowed = {"part", "block_id", "block_order", "char_start", "char_end"}
    extra = set(value) - allowed
    if extra:
        raise ValueError(f"unsupported location fields: {sorted(extra)}")
    return Location(
        part=str(value["part"]),
        block_id=str(value["block_id"]),
        block_order=int(value["block_order"]),
        char_start=int(value["char_start"]),
        char_end=int(value["char_end"]),
    )


def _atomic_json(path: Path, value: dict) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    if path.exists():
        raise FileExistsError(f"refusing to overwrite context response {path!s}")
    fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
    try:
        with os.fdopen(fd, "w", encoding="utf-8", newline="") as handle:
            json.dump(value, handle, ensure_ascii=False, indent=2, sort_keys=True)
            handle.write("\n")
            handle.flush()
            os.fsync(handle.fileno())
        os.replace(temporary, path)
    except BaseException:
        try:
            os.unlink(temporary)
        except FileNotFoundError:
            pass
        raise


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Execute one bounded definition-check context request."
    )
    parser.add_argument("input", type=Path, help="Original DOCX path")
    parser.add_argument(
        "--request", type=Path, required=True, help="One ContextRequest JSON object"
    )
    parser.add_argument(
        "--location", type=Path, help="Candidate Location JSON for expand_location"
    )
    parser.add_argument(
        "--output", type=Path, required=True, help="New JSON response path"
    )
    parser.add_argument("--work-dir", type=Path, required=True)
    parser.add_argument("--max-requests", type=int, default=8)
    parser.add_argument("--max-hops", type=int, default=3)
    parser.add_argument("--max-results", type=int, default=20)
    parser.add_argument("--max-characters", type=int, default=12_000)
    parser.add_argument("--matter-id")
    parser.add_argument("--document-id")
    parser.add_argument("--version-id")
    parser.add_argument("--snapshot-sha256")
    parser.add_argument("--expected-source-sha256")
    return parser


def main(argv: Optional[list[str]] = None) -> int:
    args = build_parser().parse_args(argv)
    try:
        work_dir = ensure_workspace(args.work_dir.resolve())[0]
        request_path = require_within_workspace(
            args.request, work_dir, label="context request"
        )
        output = require_within_workspace(
            args.output, work_dir, label="context response"
        )
        request = _request(_read_object(request_path))
        location = (
            _location(
                _read_object(
                    require_within_workspace(
                        args.location,
                        work_dir,
                        label="context request location",
                    )
                )
            )
            if args.location
            else None
        )
        budget = RetrievalBudget(
            max_requests=args.max_requests,
            max_hops=args.max_hops,
            max_results=args.max_results,
            max_characters=args.max_characters,
        )
        if (
            min(
                budget.max_requests,
                budget.max_hops,
                budget.max_results,
                budget.max_characters,
            )
            < 1
        ):
            raise ValueError("all retrieval budgets must be positive")
        source = extract_docx(args.input.resolve())
        matter_scope = (
            args.matter_id,
            args.document_id,
            args.version_id,
            args.snapshot_sha256,
        )
        if any(matter_scope) and not all(matter_scope):
            raise ValueError(
                "matter-aware retrieval requires matter, document, version, and snapshot IDs"
            )
        if args.snapshot_sha256 and not re.fullmatch(
            r"[0-9a-f]{64}", args.snapshot_sha256
        ):
            raise ValueError("snapshot SHA-256 is invalid")
        if args.expected_source_sha256 and source.sha256 != args.expected_source_sha256:
            raise ValueError(
                "source SHA-256 does not match the selected document version"
            )
        result = retrieve(source, request, RetrievalState(budget), location=location)
        payload = {
            "schema_version": CONTEXT_RESULT_SCHEMA_VERSION,
            "source": {"document_id": source.document_id, "sha256": source.sha256},
            "request": {
                "schema_version": CONTEXT_REQUEST_SCHEMA_VERSION,
                **asdict(result.request),
            },
            "evidence": [asdict(item) for item in result.evidence],
            "note": result.note,
        }
        if all(matter_scope):
            payload.update(
                {
                    "view_type": "bounded_context_retrieval",
                    "authority": "source_evidence_only",
                    "matter_id": args.matter_id,
                    "document_id": args.document_id,
                    "version_id": args.version_id,
                    "snapshot_sha256": args.snapshot_sha256,
                }
            )
            for evidence in payload["evidence"]:
                evidence["location"] = qualify_location(
                    evidence["location"],
                    matter_id=args.matter_id,
                    document_id=args.document_id,
                    version_id=args.version_id,
                )
        _atomic_json(output, payload)
    except (
        IntakeError,
        OSError,
        KeyError,
        TypeError,
        ValueError,
        WorkspaceError,
        json.JSONDecodeError,
    ) as exc:
        print(
            json.dumps(
                {"status": "failed", "error": str(exc)},
                ensure_ascii=True,
                sort_keys=True,
            ),
            file=sys.stderr,
        )
        return 2
    print(
        json.dumps(
            {
                "status": result.request.status,
                "evidence": len(result.evidence),
                "output": str(output),
            },
            sort_keys=True,
        )
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 5719804c17b415d5289f11e263b44bc22b549478bbf58fc7231e526f89bb7852