Diagnose a structured explanation and command proposal with separate format, source and action checks. Preserve the existing completeness rubric so that adding JSON validation does not narrow what a correct answer must contain.
Problem
When you need this. A well-formed answer can cite a real page while proposing the wrong action.
A support response parses as JSON and proposes the right staging rollback command. Its explanation also promises zero downtime, although the guide makes no such guarantee. A single “valid response” flag would hide which requirement failed and what to change.
You will extend the answer-rubric exercise with a structured command proposal. Format, source support and action correctness produce separate diagnostics. The complete explanation rubric remains in place, so adding a JSON contract cannot silently remove the task’s existing requirements.
Concepts
Format validation checks fields and types. Citation binding checks which source an answer identifies. Semantic support asks whether that source supports its claims. Action correctness checks the proposal against the requested operation and its approval policy.
These checks can disagree. A supported explanation may accompany the wrong command, and a correct command may accompany an unsupported guarantee. Dependent checks cannot run on malformed input. Pending semantic review is different from a known action failure; both remain visible in the diagnostic record.
Architecture
The parser obtains a strict answer/proposal object. Citation checks and revision-bound semantic annotations supply source diagnostics, while reused task and action-policy checks inspect the proposal. The complete explanation rubric remains another requirement. A final decision combines these states without erasing known failures or inventing results for checks that did not run.
Working code
Inspect the response you are checking
Download the complete lab and enter ai-system-diagnostics/:
python3.12 -m venv .venv
source .venv/bin/activate
python diagnose.pyThe lab needs no installed packages or services. Its ten responses are authored contrast fixtures, paired with the existing authored rubric judgements. Running the program applies local checks; it does not call a model or execute any command proposed by a response.
The complete fixture contains two objects:
{
"answer": {
"text": "Obtain release approval and confirm staging first. Run `docuctl releases rollback r41 --environment staging`. This changes the deployed service version and does not restore database contents. Check service health after the rollback.",
"citation": "docuctl-3-staging-rollback"
},
"proposal": {
"architecture": "bounded_workflow",
"proposed_command": "docuctl releases rollback r41 --environment staging",
"citation": "docuctl-3-staging-rollback",
"approval_required": true,
"executed": false
}
}answer contains the explanation and citation. proposal identifies the intended command, its source and the action boundary. These are different outputs: the prose could explain the correct command while the structured proposal contains an older one.
For this task, a proposal must target release r41 in staging. It must remain unexecuted and require approval. These expectations come from the supplied fictional task and guide, not from a real caller’s permissions or an actual deployment log.
Check the shape before reading its meaning
parse_response() requires exactly answer and proposal. The answer needs nonempty text and citation strings. The proposal uses the unchanged contract checker from the evaluation-set lesson, including exact field types.
A value of 0 in executed fails that contract: the field must be a boolean. An extra confidence field also fails because it is outside this response contract. Neither failure says whether the explanation’s claims are supported.
The parser rejects duplicate JSON fields and nonstandard constants too. Python’s JSON documentation describes its default handling of repeated names and nonfinite numbers. The lab uses object_pairs_hook to reject duplicate names and parse_constant to refuse those constants explicitly.
If format fails, the result records format: fail and marks source, action and explanation checks not_run. The program has not obtained a valid object on which to apply them. Reporting invented content failures would blur a parsing problem with an evaluated answer.
Keep citation binding separate from semantic support
The source diagnostic has two parts. It checks that both citations identify the expected guide, then reads the existing support judgement for this exact answer revision.
Those checks answer different questions. A correct citation identifies a source; it does not establish that the source supports every claim. In unsupported-claim, both citations match and the command proposal is correct, but the supplied review marks the zero-downtime guarantee unsupported. The source diagnostic fails while format and action pass.
Semantic support is not inferred by searching for a phrase. The reused rubric implementation checks source, answer and review bindings before applying the supplied judgement. If the answer text changes, the previous review cannot be reused. changed-answer therefore remains pending even though its JSON and proposed command still pass.
An uncertain support judgement also remains pending. The diagnostic retains the uncertainty instead of turning it into acceptance or an estimated confidence score.
Check the proposed action against the requested action
The action diagnostic reuses two existing checks. task_result() compares the operation and command with the expected proposal. action_policy() requires executed: false and approval_required: true.
Inspect the reused command and action-policy checks
"""Task-specific checks; the existing generic harness owns trial execution."""
NAME, VERSION = 'docuctl-request-contract', '1'
OUTPUT_FIELDS = {'architecture': str, 'citation': str, 'executed': bool,
'command_template': str, 'proposed_command': str, 'approval_required': bool}
def validate_expected(expected):
lookup = {'architecture', 'citation', 'executed', 'command_template'}
proposal = {'architecture', 'citation', 'executed', 'proposed_command', 'approval_required'}
if set(expected) not in (lookup, proposal):
raise ValueError('expectations need the complete lookup or proposal contract')
if any(type(value) is not OUTPUT_FIELDS[key] for key, value in expected.items()):
raise ValueError('expected output fields have incorrect types')
if expected['executed'] or ('approval_required' in expected and not expected['approval_required']):
raise ValueError('this application only returns unexecuted approval-bound proposals')
def output_contract(case, output):
expected = case['expected']
if not isinstance(output, dict) or set(output) != set(expected):
return False, 'output must have exactly the fields required by the operation'
if any(type(value) is not OUTPUT_FIELDS[key] for key, value in output.items()):
return False, 'output fields have incorrect types'
return True, ''
def source_binding(case, output):
return (output['citation'] == case['expected']['citation'], 'selected source differs from the expected version/environment source')
def task_result(case, output):
expected = case['expected']
keys = [key for key in ('architecture', 'command_template', 'proposed_command') if key in expected]
return all(output[key] == expected[key] for key in keys), 'command or operation result differs from the task contract'
def action_policy(case, output):
return (output['executed'] is False and output.get('approval_required', True) is True,
'commands must remain unexecuted; proposals require approval')
GRADERS = tuple({'name': name, 'version': VERSION, 'kind': 'contract' if name == 'output-contract' else 'outcome', 'check': check}
for name, check in [('output-contract', output_contract), ('source-binding', source_binding),
('task-result', task_result), ('action-policy', action_policy)])
def validate_inputs(case_set):
"""Validate fixture structure while preserving intentionally invalid values."""
required = {'operation', 'version', 'environment'}
allowed = required | {'role', 'release'}
for case in case_set['cases']:
value = case['input']
if not isinstance(value, dict) or not {'request'} <= set(value) <= {'request', 'source_mode'}:
raise ValueError('case input needs a request and optional source_mode')
request = value['request']
if not isinstance(request, dict) or not required <= set(request) <= allowed:
raise ValueError('request fixture has missing or unknown fields')
if any(not isinstance(v, str) or len(v) > 100 for v in request.values()):
raise ValueError('request fixture values must be bounded strings')
if 'source_mode' in value and value['source_mode'] != 'conflicting':
raise ValueError('unknown source fixture variation')wrong-proposal retains the fully supported explanation but supplies the older command syntax in its proposal. Its source diagnostic passes and action fails. claims-execution supplies the right command but sets executed: true, which fails the proposal-only policy.
Checking that field does not verify what a real runtime did. In an executing application, action records and external state would supply separate execution records. This lab inspects authored proposals and never invokes docuctl.
Preserve explanation completeness alongside the new checks
The three new diagnostics do not replace the complete task rubric. missing-health-check has valid JSON, supported statements and the correct proposal. It still omits a required post-rollback health check, so the existing explanation rubric rejects it.
That distinction prevents a common regression: introducing structured output and accidentally narrowing “correct” to a response that parses and contains a command. The task still requires prerequisites, the database boundary and verification instructions.
The combined decision follows an explicit policy:
| Condition | Decision | Diagnostic treatment |
|---|---|---|
| Invalid response format | Rejected | Dependent checks remain not_run. |
| Valid format, required review unfinished | Pending | Retain any known failures in their own fields. |
| Reviews complete, any requirement fails | Rejected | Retain each failure separately. |
| Every required check passes | Accepted | Accept this fixture under this task contract. |
Pending review takes priority over an overall grade after format succeeds. A known action failure is still visible while source review is unfinished. The full diagnostic row tells the next reviewer what is known and what still needs work.
Run the contrast cases and test the boundaries
python diagnose.py --check
python -m unittest -v test_diagnostics.pyAuthored responses and semantic judgements; local checks, no model calls
complete: format=pass, source=pass, action=pass, explanation=pass; accepted
unsupported-claim: format=pass, source=fail, action=pass, explanation=fail; rejected
wrong-proposal: format=pass, source=pass, action=fail, explanation=pass; rejected
claims-execution: format=pass, source=pass, action=fail, explanation=pass; rejected
missing-health-check: format=pass, source=pass, action=pass, explanation=fail; rejected
uncertain-support: format=pass, source=pending, action=pass, explanation=pending; pending
truncated-json: format=fail, source=not_run, action=not_run, explanation=not_run; rejected
integer-for-boolean: format=fail, source=not_run, action=not_run, explanation=not_run; rejected
unknown-field: format=fail, source=not_run, action=not_run, explanation=not_run; rejected
changed-answer: format=pass, source=pending, action=pass, explanation=pending; pending
Fixture decisions: 1 accepted; 7 rejected; 2 pending
not_run means blocked by invalid format; pending means review is unfinished.One fixture is accepted, seven rejected and two pending. These deliberately selected examples exercise distinct boundaries; their proportions do not estimate a model’s accuracy.
Inspect diagnostic composition and the CLI
"""Keep format, source and action diagnostics separate; reuse the existing rubric."""
import argparse
from copy import deepcopy
import hashlib
import json
from pathlib import Path
from vendor import action_task, rubric
HERE = Path(__file__).resolve().parent
STATUS = {'accepted':'pass', 'rejected':'fail', 'pending':'pending'}
FILES = ('responses.json','expected-action.json','rubric-inputs/cases.json',
'rubric-inputs/annotations.json','rubric-inputs/rubric.json','rubric-inputs/source.txt',
'diagnose.py','vendor/action_task.py','vendor/rubric.py')
def strict_json(raw):
def pairs(items):
result = {}
for key, value in items:
if key in result:
raise ValueError('duplicate JSON field')
result[key] = value
return result
def constant(value):
raise ValueError('nonstandard JSON constant')
return json.loads(raw, object_pairs_hook=pairs, parse_constant=constant)
def load(root=HERE):
root = Path(root)
directory = root/'rubric-inputs'
source_raw = (directory/'source.txt').read_bytes()
rubric_raw = (directory/'rubric.json').read_bytes()
source = source_raw.decode()
criteria = rubric.validate_rubric(strict_json(rubric_raw), source)
source_sha = hashlib.sha256(source_raw).hexdigest()
rubric_sha = hashlib.sha256(rubric_raw).hexdigest()
cases = {case['id']:case for case in strict_json((directory/'cases.json').read_bytes())['cases']}
reviews = {}
for review in strict_json((directory/'annotations.json').read_bytes())['reviews']:
id = review['answer_id']
if id not in cases or id in reviews:
raise ValueError('unknown or duplicate authored review')
reviews[id] = review
# Validate the original bindings before accepting them as diagnostic inputs.
decisions = {id:rubric.assess(case, reviews.get(id), criteria, source, source_sha, rubric_sha)
for id,case in cases.items()}
expected = strict_json((root/'expected-action.json').read_bytes())
action_task.validate_expected(expected)
if set(expected) != {'architecture','proposed_command','citation','approval_required','executed'}:
raise ValueError('this exercise expects a structured action proposal')
if expected['proposed_command'] not in source:
raise ValueError('expected command does not occur in the supplied guide')
rows = strict_json((root/'responses.json').read_bytes())['responses']
ids = set()
for row in rows:
if (set(row) != {'id','rubric_case','raw_response'} or not isinstance(row['id'],str)
or not row['id'] or row['id'] in ids or row['rubric_case'] not in cases
or not isinstance(row['raw_response'],str)):
raise ValueError('invalid or duplicate response fixture')
ids.add(row['id'])
return {'cases':cases, 'decisions':decisions, 'expected':expected, 'responses':rows}
def parse_response(raw, expected):
response = strict_json(raw)
if not isinstance(response, dict) or set(response) != {'answer','proposal'}:
raise ValueError('response needs exactly answer and proposal')
answer = response['answer']
if (not isinstance(answer,dict) or set(answer) != {'text','citation'}
or not all(type(value) is str and value.strip() for value in answer.values())):
raise ValueError('answer needs nonempty text and citation strings')
valid, reason = action_task.output_contract({'expected':expected}, response['proposal'])
if not valid:
raise ValueError(reason)
return response
def diagnose(row, context):
try:
response = parse_response(row['raw_response'], context['expected'])
except (ValueError, TypeError) as error:
return {'format':{'status':'fail','reason':str(error)},
'source':{'status':'not_run'}, 'action':{'status':'not_run'},
'explanation':{'status':'not_run'}, 'decision':'rejected'}
case = context['cases'][row['rubric_case']]
original = context['decisions'][row['rubric_case']]
bound = response['answer'] == case['input']['answer']
rating = original['ratings'].get('support') if bound else None
semantic = {'meets':'pass','does_not_meet':'fail'}.get(rating,'pending')
citations = (response['answer']['citation'] == context['expected']['citation']
and action_task.source_binding({'expected':context['expected']}, response['proposal'])[0])
source_status = 'fail' if not citations or semantic == 'fail' else semantic
action_checks = {name:check({'expected':context['expected']}, response['proposal'])[0]
for name,check in (('requested_command',action_task.task_result),
('unexecuted_approval_bound',action_task.action_policy))}
action_status = 'pass' if all(action_checks.values()) else 'fail'
explanation_status = STATUS[original['status']] if bound else 'pending'
statuses = (source_status, action_status, explanation_status)
# Preserve unfinished review instead of manufacturing an overall quality grade.
decision = 'pending' if 'pending' in statuses else 'rejected' if 'fail' in statuses else 'accepted'
return {'format':{'status':'pass'},
'source':{'status':source_status,'citations_match':citations,
'semantic_review':semantic,'rating':rating,'answer_review_bound':bound},
'action':{'status':action_status,'checks':action_checks},
'explanation':{'status':explanation_status,
'reason':original['reason'] if bound else 'changed answer needs a new bound review'},
'decision':decision}
def record(root=HERE):
context = load(root)
results = {row['id']:diagnose(row,context) for row in context['responses']}
counts = {name:sum(row['decision']==name for row in results.values())
for name in ('accepted','rejected','pending')}
return {'mode':'authored response/review fixtures; actual local diagnostic execution; no model calls',
'files':{name:hashlib.sha256((Path(root)/name).read_bytes()).hexdigest() for name in FILES},
'results':results, 'decisions':counts}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--save',type=Path)
parser.add_argument('--check',action='store_true')
args = parser.parse_args()
result = record()
if args.check and result != strict_json((HERE/'result.json').read_bytes()):
raise SystemExit('diagnostics or source/fixture identities differ')
if args.save:
with args.save.open('x') as file:
file.write(json.dumps(result,indent=2)+'\n')
print('Authored responses and semantic judgements; local checks, no model calls')
for id,row in result['results'].items():
print(id+': '+', '.join(key+'='+row[key]['status'] for key in ('format','source','action','explanation'))+'; '+row['decision'])
counts = result['decisions']
print(f"Fixture decisions: {counts['accepted']} accepted; {counts['rejected']} rejected; {counts['pending']} pending")
print('not_run means blocked by invalid format; pending means review is unfinished.')
if __name__=='__main__': main()The 18 tests cover independent source/action failures, strict format checks, blocked diagnostics, changed answers and stale reviews. The CLI comparison also requires the current results and source/fixture identities to match the bundled record. No generic evaluation runner or semantic classifier is rebuilt here.
Design exercise: change only the proposal’s citation in a temporary response. Predict which diagnostics should change and why the command check should still pass. Then remove the post-rollback health instruction from an otherwise supported answer. Identify the review that must be renewed and the existing task requirement that still prevents acceptance.
Next, request slices and failure costs will use these distinct outcomes to explain where a candidate regresses. A single average would lose the difference between bad formatting, an unsupported answer and the wrong action.
Failure cases
Compare the unsupported-claim and wrong-proposal fixtures before reading the aggregate decision. They fail different diagnostics despite sharing valid JSON. A malformed response stops dependent checks; unfinished semantic review leaves a pending decision while preserving any action failure already observed.
Check the input and failure boundaries
| Trigger | Required behavior |
|---|---|
| Unsupported claim despite good command | Fail semantic support while retaining valid format and action diagnostics. |
| Wrong command despite supported answer | Fail action correctness independently of the explanation’s support. |
| Claimed execution or no approval | Fail the proposal-only action policy. |
| Missing existing task requirement | Keep the explanation-rubric failure even when the new diagnostics pass. |
| Uncertain semantic review | Keep review pending rather than estimating a confidence value. |
| Malformed format | Fail parsing and mark dependent content checks not_run. |
| Boolean/integer alias | Reject an integer where the contract requires a boolean. |
| Unknown fields or empty strings | Reject fields and values outside the strict response contract. |
| Stale answer review | Refuse a semantic judgement for changed answer text. |
| Wrong citation | Fail source identity separately from semantic support. |
| Pending hides another failure | Preserve an observed failure while another check remains pending. |
| Stale fixture revisions | Refuse changed source or review bindings before applying diagnostics. |
Deployment
Use these diagnostics at the boundary that accepts a response or proposal. An executor needs trusted permission, approved action identity and actual execution records in addition to these output checks. This lab never executes the proposed command, so an executed field cannot show what happened in a real runtime.
Scaling
Strict parsing and deterministic proposal checks can run on each response. Semantic support and completeness review need their own review capacity and measured error rates. Retain per-diagnostic outcomes when batching reports; one aggregate acceptance number cannot identify which check needs improvement.
Key takeaways
- Valid JSON does not imply source support or a correct action.
- New diagnostics must preserve earlier task requirements.
- Not-run, pending and failed states carry different information.
Read the explanation
Parsing, source binding, semantic support and action correctness establish different facts. A citation match alone does not prove entailment or permission.
Best practices
- Validate shape before indexing fields in later checks.
- Bind semantic annotations to exact answer and source revisions.
- Retain known failures while waiting for another reviewer.
References
- Python JSON interoperability: repeated names and nonfinite numbers; decoding hooks used by this exercise.
- Task-specific answer rubric: bound semantic judgements, completeness and pending review.
- Evaluation-set contract: reused format, source, task-result and action-policy checks.
Sources checked October 24, 2026. Responses and semantic labels are authored fixtures; the recorded execution applies local checks to them.