209 lines
12 KiB
Python
209 lines
12 KiB
Python
"""Internal multi-pass schemas, prompts, and validation; no client fields added."""
|
|
from __future__ import annotations
|
|
|
|
import copy
|
|
import re
|
|
from collections import Counter
|
|
|
|
from suite_contract import FIELDS, Problem, SCHEMA, SYSTEM, encoded, validate_partition
|
|
from suite_assignments import WIRE_INSTRUCTIONS, decision_keys, wire_schema
|
|
|
|
POLICY_REVISION = "implementation-five-v1-20260929"
|
|
MAX_GROUP = 5
|
|
BASE_NAME_LIMIT = 56 # Leaves room for ' (80/80)' at the 400-case input limit.
|
|
STAGES = ("proposal_a", "proposal_b", "reconciliation", "large_family_review", "decision_audit")
|
|
|
|
DISCOVERY = (
|
|
"Discover natural implementation families across this COMPLETE suite, without any "
|
|
"task-size cap. Do not split a coherent family for a numerical quota. Names normally "
|
|
"use two to five words describing test machinery; never repeat the hierarchy. "
|
|
"Use distinct meaningful mechanism names, not numbering to hide collisions. "
|
|
"Use StructuredOutput directly when available; do not first emit a prose draft."
|
|
)
|
|
RECONCILE = (
|
|
"Independently reassess two complete candidate partitions using ALL original cases. "
|
|
"Compare memberships, never proposal names or group numbers. Resolve disagreements "
|
|
"against success_criteria and shared test machinery. Do not vote, count majority "
|
|
"co-memberships, or take transitive connected components. Agreement is not proof. "
|
|
"Return one coherent NATURAL partition, with no size cap. Review cross-family merges "
|
|
"and unjustified splits. Record concise engineering rationales and unresolved "
|
|
"uncertainty, not private reasoning traces. Evidence must quote a short exact substring "
|
|
"from a named source field of an assigned alias. Prefer success_criteria when present. "
|
|
"common_work must describe ONLY machinery applicable to EVERY assigned member, "
|
|
"without enumerating objectives/variations that apply to only some members. "
|
|
"variation_sets partition each family's aliases into closely related variations; "
|
|
"these are ordering hints, not additional task levels or semantic families. "
|
|
"Keep identical case content together in variation_sets. Never impose five-member "
|
|
"limits on natural discovery. Use unique mechanism names, normally two to five words "
|
|
"and at most 56 characters, without numbered suffixes. Rewrite long/colliding names "
|
|
"meaningfully; do not blindly truncate or add arbitrary identifiers. "
|
|
"Use StructuredOutput directly when available; no prose draft."
|
|
)
|
|
REVIEW = (
|
|
"Review EVERY listed oversized family against its original source case fields, "
|
|
"especially success_criteria. Decide whether different control/measurement/evidence "
|
|
"mechanisms warrant separate natural families with meaningful distinct names. "
|
|
"Do not split by source order, quota, nominal/fault labels alone, or cheap assertion "
|
|
"variations. If coherent, explicitly explain the common machinery and why differences "
|
|
"are inexpensive variations. Preserve that coherent natural family even if it has "
|
|
"more than five members: deterministic work sizing happens later. Return a complete "
|
|
"natural partition of the whole suite and one explicit decision per reviewed family. "
|
|
"Decisions refer to the exact reviewed source_members, not names. Splits must partition "
|
|
"that family's members; do not cross existing family boundaries during this review. "
|
|
"Retain and improve concise source-field support and uncertainty."
|
|
)
|
|
AUDIT = (
|
|
"Perform ONE bounded additional review of the previous large-family decisions. "
|
|
"Using all original fields, challenge unjustified semantic splits, unjustified broad "
|
|
"merges, and assertions that would need different machinery. You may correct the "
|
|
"decision by merging/splitting only within each ORIGINAL oversized family. Re-examine "
|
|
"all its descendants even if each is now small. Return the complete final NATURAL "
|
|
"partition and final decisions for every original oversized family. There will be "
|
|
"no loop seeking agreement; explicitly retain unresolved uncertainty. Preserve "
|
|
"coherent large families for deterministic capacity division afterwards."
|
|
)
|
|
|
|
|
|
def text_schema(limit):
|
|
"""Return a bounded string schema for internal model output."""
|
|
return {"type": "string", "minLength": 1, "maxLength": limit}
|
|
|
|
|
|
ALIAS_LIST = {"type": "array", "minItems": 1, "items": text_schema(53)}
|
|
EVIDENCE = {"type": "array", "minItems": 1, "maxItems": 3, "items": {
|
|
"type": "object", "additionalProperties": False,
|
|
"required": ["alias", "field", "quote"], "properties": {
|
|
"alias": text_schema(53), "field": {"type": "string", "enum": sorted(FIELDS)},
|
|
"quote": text_schema(160)}}}
|
|
DISCOVERY_SCHEMA = copy.deepcopy(SCHEMA)
|
|
DISCOVERY_SCHEMA["properties"]["groups"]["items"]["properties"]["members"].pop("maxItems", None)
|
|
DISCOVERY_SCHEMA["properties"]["groups"]["items"]["properties"]["name"]["maxLength"] = BASE_NAME_LIMIT
|
|
NATURAL_SCHEMA = copy.deepcopy(DISCOVERY_SCHEMA)
|
|
GROUP_PROPERTIES = NATURAL_SCHEMA["properties"]["groups"]["items"]["properties"]
|
|
GROUP_PROPERTIES["name"]["maxLength"] = BASE_NAME_LIMIT
|
|
GROUP_PROPERTIES.update({
|
|
"common_work": text_schema(160), "rationale": text_schema(320),
|
|
"uncertainty": {"type": "string", "maxLength": 240}, "evidence": EVIDENCE,
|
|
"variation_sets": {"type": "array", "minItems": 1, "items": ALIAS_LIST},
|
|
})
|
|
NATURAL_SCHEMA["properties"]["groups"]["items"]["required"] = list(GROUP_PROPERTIES)
|
|
REVIEW_SCHEMA = copy.deepcopy(NATURAL_SCHEMA)
|
|
REVIEW_SCHEMA["required"].append("decisions")
|
|
REVIEW_SCHEMA["properties"]["decisions"] = {"type": "array", "minItems": 1, "items": {
|
|
"type": "object", "additionalProperties": False,
|
|
"required": ["source_members", "decision", "rationale", "evidence"], "properties": {
|
|
"source_members": ALIAS_LIST, "decision": {"type": "string", "enum": ["keep", "split"]},
|
|
"rationale": text_schema(320), "evidence": EVIDENCE}}}
|
|
|
|
|
|
def invocation(stage, request, context=None):
|
|
"""Build one full-input pass; independent discovery has no proposal context."""
|
|
if stage not in STAGES:
|
|
raise ValueError("unknown internal stage")
|
|
discovery = stage in STAGES[:2]
|
|
if discovery and context:
|
|
raise ValueError("discovery must be independent")
|
|
schema = DISCOVERY_SCHEMA if discovery else NATURAL_SCHEMA if stage == "reconciliation" else REVIEW_SCHEMA
|
|
instruction = DISCOVERY if discovery else RECONCILE
|
|
if stage == "large_family_review":
|
|
instruction += "\n" + REVIEW
|
|
if stage == "decision_audit":
|
|
instruction += "\n" + REVIEW + "\n" + AUDIT
|
|
source = {k: request[k] for k in ("campaign", "suite", "cases")}
|
|
context = dict(context or {})
|
|
keys = decision_keys(context) if stage in STAGES[3:] else {}
|
|
if keys:
|
|
context["review_keys"] = keys
|
|
return {"stage": stage,
|
|
"schema": wire_schema(schema, request, context), "review_keys": keys,
|
|
"input": encoded({"suite": source, "review_material": context}).decode(),
|
|
"system": SYSTEM + "\n\nPASS INSTRUCTIONS:\n" + instruction + "\n\n" + WIRE_INSTRUCTIONS}
|
|
|
|
|
|
def projection(value):
|
|
"""Extract only the unchanged public group shape from a natural partition."""
|
|
return {"groups": [{k: group[k] for k in ("name", "description", "members")}
|
|
for group in value["groups"]]}
|
|
|
|
|
|
def bounded_string(value, limit, *, empty=False):
|
|
"""Reject invalid internal strings without echoing their contents."""
|
|
if type(value) is not str or len(value) > limit or (not empty and not value.strip()):
|
|
raise Problem("invalid_review_output", 502)
|
|
|
|
|
|
def evidence(value, members, by_alias):
|
|
"""Require short exact source-field support within the explained membership."""
|
|
if not isinstance(value, list) or not 1 <= len(value) <= 3:
|
|
raise Problem("invalid_review_evidence", 502)
|
|
for item in value:
|
|
if not isinstance(item, dict) or set(item) != {"alias", "field", "quote"}:
|
|
raise Problem("invalid_review_evidence", 502)
|
|
alias, field, quote = item["alias"], item["field"], item["quote"]
|
|
if type(alias) is not str or alias not in members or type(field) is not str or field not in FIELDS:
|
|
raise Problem("invalid_review_evidence", 502)
|
|
bounded_string(quote, 160)
|
|
source = by_alias[alias].get(field)
|
|
if not isinstance(source, str) or quote not in source:
|
|
raise Problem("invalid_review_evidence", 502)
|
|
|
|
|
|
def validate_natural(value, request, originals=None):
|
|
"""Validate complete natural assignments and bounded explanations, without a cap."""
|
|
expected_keys = {"groups", "decisions"} if originals is not None else {"groups"}
|
|
if not isinstance(value, dict) or set(value) != expected_keys or not isinstance(value["groups"], list):
|
|
raise Problem("invalid_review_output", 502)
|
|
required = set(GROUP_PROPERTIES)
|
|
for group in value["groups"]:
|
|
if not isinstance(group, dict) or set(group) != required:
|
|
raise Problem("invalid_review_output", 502)
|
|
validate_partition(projection(value), request, name_limit=BASE_NAME_LIMIT)
|
|
by_alias = {case["alias"]: case for case in request["cases"]}
|
|
for group in value["groups"]:
|
|
members = set(group["members"])
|
|
for field, limit in (("common_work", 160), ("rationale", 320), ("uncertainty", 240)):
|
|
bounded_string(group[field], limit, empty=field == "uncertainty")
|
|
if re.search(r"\(\s*\d+\s*/\s*\d+\s*\)$", group["name"]):
|
|
raise Problem("invalid_natural_family_name", 502)
|
|
evidence(group["evidence"], members, by_alias)
|
|
variations = group["variation_sets"]
|
|
if not isinstance(variations, list) or not variations or any(
|
|
not isinstance(part, list) or not part or any(type(a) is not str for a in part)
|
|
for part in variations):
|
|
raise Problem("invalid_variation_assignments", 502)
|
|
if Counter(a for part in variations for a in part) != Counter(group["members"]):
|
|
raise Problem("invalid_variation_assignments", 502)
|
|
if originals is not None:
|
|
validate_decisions(value, originals, by_alias)
|
|
return value
|
|
|
|
|
|
def validate_decisions(value, originals, by_alias):
|
|
"""Audit every original oversized family and forbid cross-family review drift."""
|
|
decisions = value["decisions"]
|
|
large = {frozenset(g["members"]): g for g in originals if len(g["members"]) > MAX_GROUP}
|
|
if not isinstance(decisions, list) or len(decisions) != len(large):
|
|
raise Problem("incomplete_large_family_review", 502)
|
|
seen = set()
|
|
for decision in decisions:
|
|
if not isinstance(decision, dict) or set(decision) != {"source_members", "decision", "rationale", "evidence"}:
|
|
raise Problem("invalid_review_output", 502)
|
|
members = decision["source_members"]
|
|
if not isinstance(members, list) or any(type(a) is not str for a in members):
|
|
raise Problem("invalid_review_output", 502)
|
|
key = frozenset(members)
|
|
if key not in large or key in seen or len(key) != len(members):
|
|
raise Problem("incomplete_large_family_review", 502)
|
|
seen.add(key)
|
|
descendants = [g for g in value["groups"] if set(g["members"]) <= key]
|
|
split = len(descendants) > 1
|
|
if not descendants or decision["decision"] != ("split" if split else "keep"):
|
|
raise Problem("invalid_review_decision", 502)
|
|
bounded_string(decision["rationale"], 320)
|
|
evidence(decision["evidence"], key, by_alias)
|
|
for group in value["groups"]:
|
|
parents = [g for g in originals if set(group["members"]) <= set(g["members"])]
|
|
if len(parents) != 1 or (len(parents[0]["members"]) <= MAX_GROUP and
|
|
set(group["members"]) != set(parents[0]["members"])):
|
|
raise Problem("review_crossed_family_boundary", 502)
|