import {
CompletionRequirement,
CompletionRequirementContract,
CompletionRequirementEvidenceRole,
} from "std/agent/options_types"
pub type CompletionRequirementAssessment = {
requirement_id: string,
met: bool,
evidence_refs: list<int>,
}
pub type CompletionRequirementRowStatus = "met" | "unmet" | "missing" | "unsupported"
pub type CompletionRequirementStatus = {
requirement_id: string,
name: string,
met: bool,
evidence_refs: list<int>,
status: CompletionRequirementRowStatus,
// The evidence kinds this item accepts, carried on the row so pending
// feedback can name them. Without this a rejected item can only be echoed
// back as its own text, which tells the actor nothing it did not already know.
evidence_roles: list<CompletionRequirementEvidenceRole>,
}
pub type CompletionRequirementReport = {
schema: "harn.completion_requirement_report.v1",
complete: bool,
required_count: int,
reported_count: int,
pending_count: int,
pending_names: list<string>,
rows: list<CompletionRequirementStatus>,
}
/** One typed, bounded fact a requirement assessment may cite. */
pub type CompletionRequirementEvidenceRecord = {
evidence_index: int,
role: CompletionRequirementEvidenceRole,
supports_completion: bool,
summary: string,
representative_artifact_ids: list<string>,
artifact_count?: int,
artifact_set_digest?: string,
measurement?: string,
}
// The judge is shown one packet whose `actions` and `requirement_evidence`
// both carry a field named `evidence_index`, in two disjoint namespaces
// (actions count up from zero; typed records count down from -1). A model that
// cites a real tool action alongside a typed record is reading the packet it
// was given, so an action index has to be recognized here rather than read as
// a citation of nothing.
pub type CompletionRequirementEvidencePacket = {
requirement_evidence?: list<CompletionRequirementEvidenceRecord>,
actions?: list,
fallback_observations?: list,
}
fn completion_requirement_evidence_role(value: unknown) -> CompletionRequirementEvidenceRole? {
if value == "assistant_output" {
return "assistant_output"
}
if value == "deterministic_verification" {
return "deterministic_verification"
}
if value == "mutation_summary" {
return "mutation_summary"
}
return nil
}
fn completion_requirement_evidence_roles(raw: unknown) -> list<CompletionRequirementEvidenceRole> {
if raw == nil {
// `mutation_summary` is deliberately NOT in the default. The packet ships
// one so a requirement that opts in via `evidence_roles` can cite it;
// having changed a file is not evidence that an undeclared requirement was
// met. See `test_omitted_roles_do_not_let_mutation_summary_prove_any_requirement`.
let all_roles: list<CompletionRequirementEvidenceRole> = []
all_roles = all_roles + ["assistant_output"]
return all_roles + ["deterministic_verification"]
}
let roles: list<CompletionRequirementEvidenceRole> = []
if type_of(raw) != "list" {
return roles
}
for value in raw {
const role = completion_requirement_evidence_role(value)
if role != nil && !roles.contains(role) {
roles = roles + [role]
}
}
return roles
}
/**
* Validate and normalize the optional requirement ledger. One owner for the
* acceptance-ledger policy: every surface that accepts a raw contract routes
* here, so a malformed ledger fails closed with one vocabulary instead of
* silently shrinking the set of requirements the completion gate enforces.
*
* `label` names the option the raw value arrived under, so a caller-facing
* message points at the field the author wrote.
*
* @effects: []
* @errors: [validation]
* @api_stability: internal
*/
pub fn __completion_requirement_contract_checked(
label: string,
value: any,
) -> CompletionRequirementContract? {
if value == nil {
return nil
}
if type_of(value) != "dict" {
throw "agent_loop: `" + label + ".requirement_contract` must be a dict or nil; got "
+ type_of(value)
}
const raw_requirements = value?.requirements
if type_of(raw_requirements) != "list" || len(raw_requirements) == 0 {
throw "agent_loop: `" + label
+ ".requirement_contract.requirements` must be a non-empty list"
}
let requirements: list<CompletionRequirement> = []
let seen: dict = {}
for raw in raw_requirements {
if type_of(raw) != "dict" {
throw "agent_loop: every `" + label
+ ".requirement_contract.requirements` item must be a dict"
}
const requirement_id = trim(to_string(raw?.requirement_id ?? ""))
const name = trim(to_string(raw?.name ?? ""))
if requirement_id == "" || name == "" {
throw "agent_loop: every completion requirement needs non-empty `requirement_id` and `name`"
}
if seen[requirement_id] ?? false {
throw "agent_loop: duplicate completion `requirement_id` `" + requirement_id + "`"
}
seen = seen + {[requirement_id]: true}
const raw_roles = raw?.evidence_roles
if raw_roles != nil {
if type_of(raw_roles) != "list" || len(raw_roles) == 0 {
throw "agent_loop: completion requirement `evidence_roles` must be a non-empty list"
}
let seen_roles: dict = {}
for value_role in raw_roles {
if completion_requirement_evidence_role(value_role) == nil {
throw "agent_loop: unsupported completion requirement evidence role `"
+ to_string(value_role)
+ "`"
}
if seen_roles[value_role] ?? false {
throw "agent_loop: duplicate completion requirement evidence role `"
+ to_string(value_role)
+ "`"
}
seen_roles = seen_roles + {[value_role]: true}
}
}
const roles = completion_requirement_evidence_roles(raw_roles)
requirements = requirements
+ [{requirement_id: requirement_id, name: name, evidence_roles: roles}]
}
return {requirements: requirements}
}
/**
* Return the optional requirement ledger, normalized to its public shape.
* A malformed ledger throws rather than dropping requirements, so the
* completion gate can never be weakened by a contract it could not read.
*
* @effects: []
* @errors: [validation]
* @api_stability: experimental
* @example: completion_requirement_contract({requirements: [{requirement_id: "build", name: "Build the artifact."}]})
*/
pub fn completion_requirement_contract(value: unknown) -> CompletionRequirementContract? {
return __completion_requirement_contract_checked("completion_judge", value)
}
/**
* JSON-schema projection accepted by the existing structured judge call.
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: completion_requirement_assessments_schema(contract)
*/
pub fn completion_requirement_assessments_schema(
contract: CompletionRequirementContract?,
) -> dict? {
if contract == nil || len(contract.requirements) == 0 {
return nil
}
return {
type: "array",
description:
"One assessment per required item. Cite only requirement_evidence indices whose role is allowed by that item.",
items: {
type: "object",
properties: {
requirement_id: {type: "string"},
met: {type: "boolean"},
evidence_refs: {type: "array", items: {type: "integer"}},
},
required: ["requirement_id", "met", "evidence_refs"],
additionalProperties: false,
},
}
}
/**
* Stable requirement text added to the one completion judge's input.
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: completion_requirement_contract_prompt(contract)
*/
pub fn completion_requirement_contract_prompt(contract: CompletionRequirementContract?) -> string {
if contract == nil || len(contract.requirements) == 0 {
return ""
}
return "\n\nAtomic completion requirements:\n"
+ json_stringify(contract.requirements)
+ "\nReturn one requirement_report row for every requirement_id. Mark met only when requirement_evidence semantically supports that item, and cite indices with an allowed evidence role."
}
fn completion_requirement_strict_refs(raw: unknown) -> list<int> {
let refs: list<int> = []
if type_of(raw) != "list" {
return refs
}
for value in raw {
if type_of(value) == "int" && !refs.contains(value) {
refs = refs + [value]
}
}
return refs
}
fn completion_requirement_refs_well_formed(raw: unknown) -> bool {
if type_of(raw) != "list" {
return false
}
for value in raw {
if type_of(value) != "int" {
return false
}
}
return true
}
fn completion_requirement_evidence_matches(
packet: CompletionRequirementEvidencePacket,
evidence_index: int,
) -> list<CompletionRequirementEvidenceRecord> {
let matches: list<CompletionRequirementEvidenceRecord> = []
for record in packet.requirement_evidence ?? [] {
if type_of(record?.evidence_index) == "int" && record.evidence_index == evidence_index {
matches = matches + [record]
}
}
return matches
}
fn completion_requirement_action_indices(packet: CompletionRequirementEvidencePacket) -> list<int> {
let indices: list<int> = []
for action in (packet.actions ?? []) + (packet.fallback_observations ?? []) {
if type_of(action?.evidence_index) == "int" && !indices.contains(action.evidence_index) {
indices = indices + [action.evidence_index]
}
}
return indices
}
fn completion_requirement_record_qualifies(
record: CompletionRequirementEvidenceRecord,
allowed_roles: list<CompletionRequirementEvidenceRole>,
) -> bool {
const role = completion_requirement_evidence_role(record?.role)
if role == nil || !allowed_roles.contains(role) || record?.supports_completion != true
|| type_of(record?.summary) != "string"
|| trim(record.summary) == ""
|| type_of(record?.representative_artifact_ids) != "list" {
return false
}
if role == "deterministic_verification" && record?.measurement != "passed" {
return false
}
if role == "mutation_summary"
&& (type_of(record?.artifact_count) != "int" || record.artifact_count < 1
|| type_of(record?.artifact_set_digest) != "string"
|| trim(record.artifact_set_digest) == "") {
return false
}
return true
}
/**
* A row is supported when at least one citation resolves to a typed record
* with an allowed role. A citation of a tool action the packet itself listed is
* corroborating, not disqualifying: the two namespaces share the field name
* `evidence_index`, so treating an action reference as a citation of nothing
* rejects a well-formed assessment for a wire-vocabulary collision. A citation
* that resolves to nothing at all, or to a record whose role is disallowed or
* whose reading contradicts completion, still fails the row.
*/
fn completion_requirement_refs_supported(
refs: list<int>,
requirement: CompletionRequirement,
packet: CompletionRequirementEvidencePacket,
) -> bool {
if len(refs) == 0 {
return false
}
const allowed_roles = completion_requirement_evidence_roles(requirement.evidence_roles)
if len(allowed_roles) == 0 {
return false
}
const action_indices = completion_requirement_action_indices(packet)
let qualifying = 0
for evidence_index in refs {
const matches = completion_requirement_evidence_matches(packet, evidence_index)
if len(matches) > 1 {
return false
}
if len(matches) == 1 {
// Indexing is fallible to the checker even where the length is known, and
// qualification takes a record rather than a maybe-record. Iterating the
// one-element list binds the element at its declared type instead of
// re-validating what `completion_requirement_evidence_matches` already
// guarantees.
for record in matches {
if !completion_requirement_record_qualifies(record, allowed_roles) {
return false
}
}
qualifying = qualifying + 1
continue
}
if !action_indices.contains(evidence_index) {
return false
}
}
return qualifying >= 1
}
/**
* A deterministic verifier that ran and passed is a runtime fact, not a claim
* the judge establishes. When an item accepts verification evidence and the
* packet carries a passing reading, the item is satisfied by that receipt, so a
* citation the model formatted badly cannot veto settled work. The model must
* still report the item met; this removes the citation trap, not the judgment.
*/
fn completion_requirement_verified_by_receipt(
requirement: CompletionRequirement,
packet: CompletionRequirementEvidencePacket,
) -> bool {
const allowed_roles = completion_requirement_evidence_roles(requirement.evidence_roles)
// The receipt settles an item whose evidence kind IS verification, never one
// that merely permits verification among other kinds. The default role set
// contains `deterministic_verification` alongside `assistant_output`, so
// testing only for its presence would let a passing verifier anywhere in the
// session satisfy "Answer the question." — an item the verifier says nothing
// about. Requiring the author to have narrowed the item to verification
// evidence is the opt-in that keeps this a binding, not a bypass.
// See `test_omitted_roles_do_not_let_mutation_summary_prove_any_requirement`.
if !allowed_roles.contains("deterministic_verification")
|| allowed_roles.contains("assistant_output") {
return false
}
for record in packet.requirement_evidence ?? [] {
if completion_requirement_evidence_role(record?.role) == "deterministic_verification"
&& record?.measurement == "passed"
&& record?.supports_completion == true {
return true
}
}
return false
}
fn completion_requirement_matches(raw_rows: unknown, requirement_id: string) -> list {
let matches = []
if type_of(raw_rows) != "list" {
return matches
}
for row in raw_rows {
if trim(to_string(row?.requirement_id ?? "")) == requirement_id {
matches = matches + [row]
}
}
return matches
}
/**
* Join model assessments to the authoritative ledger and evidence packet.
* A scalar `done` cannot collapse a missing, duplicate, unmet, or uncited row.
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: completion_requirement_report(contract, assessments, evidence_packet)
*/
pub fn completion_requirement_report(
contract: CompletionRequirementContract?,
raw_rows: unknown,
evidence_packet: CompletionRequirementEvidencePacket,
) -> CompletionRequirementReport? {
if contract == nil || len(contract.requirements) == 0 {
return nil
}
let rows: list<CompletionRequirementStatus> = []
let pending_names: list<string> = []
let authoritative_ids: list<string> = []
for requirement in contract.requirements {
authoritative_ids = authoritative_ids + [requirement.requirement_id]
}
for requirement in contract.requirements {
const matches = completion_requirement_matches(raw_rows, requirement.requirement_id)
let refs: list<int> = []
let status: CompletionRequirementRowStatus = "missing"
if len(matches) == 1 {
const raw = matches[0]
refs = completion_requirement_strict_refs(raw?.evidence_refs)
if type_of(raw?.met) != "bool" || raw.met != true {
status = "unmet"
} else if completion_requirement_refs_well_formed(raw?.evidence_refs)
&& completion_requirement_refs_supported(refs, requirement, evidence_packet) {
status = "met"
} else if completion_requirement_verified_by_receipt(requirement, evidence_packet) {
status = "met"
} else {
status = "unsupported"
}
} else if len(matches) > 1 {
status = "unsupported"
}
const met = status == "met"
if !met {
pending_names = pending_names + [requirement.name]
}
rows = rows
+ [
{
requirement_id: requirement.requirement_id,
name: requirement.name,
met: met,
evidence_refs: refs,
status: status,
evidence_roles: completion_requirement_evidence_roles(requirement.evidence_roles),
},
]
}
if type_of(raw_rows) == "list" {
for raw in raw_rows {
const reported_id = trim(to_string(raw?.requirement_id ?? ""))
if reported_id == "" || !authoritative_ids.contains(reported_id) {
pending_names = pending_names
+ [
reported_id == "" ? "Invalid requirement row" : "Unknown requirement `" + reported_id
+ "`",
]
}
}
}
return {
schema: "harn.completion_requirement_report.v1",
complete: len(pending_names) == 0,
required_count: len(contract.requirements),
reported_count: type_of(raw_rows) == "list" ? len(raw_rows) : 0,
pending_count: len(pending_names),
pending_names: pending_names,
rows: rows,
}
}
/**
* Plain, bounded feedback for an incomplete atomic-requirement report.
*
* @effects: []
* @errors: []
* @api_stability: experimental
* @example: completion_requirement_pending_feedback(report)
*/
fn completion_requirement_status_phrase(status: CompletionRequirementRowStatus) -> string {
if status == "unmet" {
return "reported not met"
}
if status == "missing" {
return "not assessed"
}
if status == "unsupported" {
return "claimed met without usable evidence"
}
return "met"
}
fn completion_requirement_role_names(
roles: list<CompletionRequirementEvidenceRole>,
) -> list<string> {
let names: list<string> = []
for role in roles {
names = names + [to_string(role)]
}
return names
}
fn completion_requirement_clip_name(name: string) -> string {
const limit = 180
if len(name) <= limit {
return name
}
return substring(name, 0, limit) + "…"
}
pub fn completion_requirement_pending_feedback(report: CompletionRequirementReport) -> string {
let parts: list<string> = []
let row_names: list<string> = []
for row in report.rows {
row_names = row_names + [row.name]
if row.met {
continue
}
parts = parts
+ [
"`" + row.requirement_id + "` is " + completion_requirement_status_phrase(row.status)
+ " — "
+ completion_requirement_clip_name(row.name)
+ " Accepted evidence kinds: "
+ join(completion_requirement_role_names(row.evidence_roles), ", ")
+ ".",
]
}
for name in report.pending_names {
if !row_names.contains(name) {
parts = parts + [name + "."]
}
}
return "Completion rejected: " + to_string(report.pending_count)
+ " of "
+ to_string(report.required_count)
+ " acceptance item(s) still pending. "
+ join(parts, " ")
}