Write down the facts and the allowed shape.
Our authored source facts: project Harbor, owner Morgan, due date 2026-10-14, review pending, next action hold. These are invented practice records, not customer data or model outputs.
The receiving tool expects exactly five fields. For this tutorial, Morgan and Riley are both allowed owner names; only source comparison establishes which one owns this project. The status may be pending or approved. If it is pending, the next action must be hold.
The JSON Schema object guide explains why listing properties alone does not require them. We add required and additionalProperties: false to make omissions and unexpected fields visible.
Start in an empty practice folder.
Install Python, open PowerShell in a new empty folder, and run:
python --version
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install 'jsonschema==4.26.0'jsonschema 4.26.0 requires Python 3.10 or newer. We ran this exercise on Windows with Python 3.13.15. The top-level package is pinned; its dependencies are not fully locked. Installation downloads packages.
The virtual environment keeps packages in the practice folder; it does not sandbox execution or change permissions. Calling its interpreter directly avoids changing PowerShell activation policy.
Download the complete exercise.
The exercise ZIP contains a schema, the full checker, six authored JSON files, an observed report, a README, and a proposed starter prompt. Extract it, then read the code and rules before running:
Invoke-WebRequest -Uri 'https://jabsystems.io/ai-anonymous/Social/valid-json-exercise.zip' -OutFile .\valid-json-exercise.zip
Expand-Archive -LiteralPath .\valid-json-exercise.zip -DestinationPath .\exercise
Get-Content -LiteralPath .\exercise\validate-handoff.py
Get-Content -LiteralPath .\exercise\handoff-schema.jsonThe declared contract
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Fictional project handoff",
"type": "object",
"properties": {
"project": {
"type": "string",
"const": "Harbor"
},
"owner": {
"type": "string",
"enum": [
"Morgan",
"Riley"
]
},
"due_date": {
"type": "string",
"format": "date"
},
"review_status": {
"type": "string",
"enum": [
"pending",
"approved"
]
},
"next_action": {
"type": "string",
"enum": [
"hold",
"prepare-export"
]
}
},
"required": [
"project",
"owner",
"due_date",
"review_status",
"next_action"
],
"additionalProperties": false,
"if": {
"properties": {
"review_status": {
"const": "pending"
}
},
"required": [
"review_status"
]
},
"then": {
"properties": {
"next_action": {
"const": "hold"
}
}
}
}
The conditional guide describes if and then. Here the condition checks pending review and the consequence requires hold. This rule checks consistency inside the response; it does not verify that an approved status came from a real reviewer.
The string guide documents the date format. In Python jsonschema, format checking needs an explicit checker. Our code enables FormatChecker(formats=["date"]); the library documentation lists date as a format without an extra dependency.
The full checker
"""Check fictional handoff files locally; never execute a handoff action."""
import argparse
import json
from importlib.metadata import version
from pathlib import Path
from jsonschema import Draft202012Validator, FormatChecker
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValueError(f"Duplicate object key: {key}")
result[key] = value
return result
def reject_constant(value):
raise ValueError(f"Non-JSON constant: {value}")
def read_json(path):
return json.loads(path.read_text(encoding="utf-8-sig"),
object_pairs_hook=unique_object,
parse_constant=reject_constant)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("files", nargs="+", type=Path)
args = parser.parse_args()
try:
schema = read_json(Path(__file__).with_name("handoff-schema.json"))
Draft202012Validator.check_schema(schema)
except (OSError, ValueError) as error:
parser.error(f"Cannot load schema: {error}")
validator = Draft202012Validator(schema, format_checker=FormatChecker(formats=["date"]))
results = []
for path in args.files:
try:
instance = read_json(path)
except (OSError, ValueError) as error:
results.append({"file": path.name, "status": "input-error", "error": str(error)})
continue
errors = sorted(validator.iter_errors(instance), key=lambda e: (e.json_path, e.message))
results.append({"file": path.name, "status": "pass" if not errors else "schema-fail",
"errors": [{"path": e.json_path, "rule": e.validator,
"message": e.message} for e in errors]})
print(json.dumps({"jsonschema": version("jsonschema"), "draft": "2020-12",
"scope": "syntax and declared rules only; no fact verification or actions",
"results": results}, indent=2))
if any(item["status"] == "input-error" for item in results):
return 2
return 1 if any(item["status"] == "schema-fail" for item in results) else 0
if __name__ == "__main__":
raise SystemExit(main())
The checker loads the bundled schema beside itself, validates that schema, and reports every schema error for each input. It never dispatches next_action. There are no external schema references or model requests in these files.
Python’s JSON decoder documentation explains its default treatment of repeated keys and non-finite constants. Our loader deliberately rejects duplicate keys and NaN/Infinity, accepts UTF-8 with an optional BOM, and requires raw JSON rather than Markdown code fences.
Give the contract something to reject.
.\.venv\Scripts\python.exe .\exercise\validate-handoff.py .\exercise\handoff-good.json `
.\exercise\handoff-missing-owner.json `
.\exercise\handoff-extra-field.json `
.\exercise\handoff-invalid-date.json `
.\exercise\handoff-premature-action.json `
.\exercise\handoff-wrong-fact.json
$LASTEXITCODEThe command intentionally exits 1: four fixtures violate the schema. Run only the good file for exit 0:
.\.venv\Scripts\python.exe .\exercise\validate-handoff.py .\exercise\handoff-good.json
$LASTEXITCODEExit 0 means all supplied files passed the schema. Exit 1 means a schema failure. Exit 2 means an input or command error, such as malformed JSON or a missing file. Read the report before drawing a conclusion.
What actually happened on these downloads
handoff-good.json: passes the schema and matches the fictional facts.handoff-missing-owner.json: failsrequired.handoff-extra-field.json: failsadditionalPropertiesfor an inventedsend_emailfield.handoff-invalid-date.json: failsformatfor February 30.handoff-premature-action.json: failsconstbecause pending review requires hold.handoff-wrong-fact.json: passes the schema, but names Riley instead of the source’s Morgan.
Inspect the observed JSON report. Two schema passes and four deliberate failures describe these authored fixtures only; they do not measure AI accuracy.
Keep the check between the response and the action.
- Use the consumer’s contract. Define required fields and permitted values from the receiving tool’s needs. An extra field is not authorization to execute it.
- Validate the raw response. Stop on parse errors, schema failures, refusals, or missing required facts. Ask for correction or review. Bound retries instead of inventing values to force a pass.
- Compare with trusted sources. Verify the owner, date, and review state independently. If a fact is authoritative and fixed for the job, a narrower rule can help, but only a trusted source can establish that rule.
- Authorize side effects separately. Keep approval in trusted application state and enforce permissions in the executing tool. A model-written string saying approved is not evidence of human approval.
The starter prompt is an optional next experiment. It was not submitted to a model. If you try it, attach the schema, use the fictional facts, validate the raw reply, and check every fact before integrating it with work.
Passing rules does not establish truth.
Verified: installation and execution in the stated Windows/Python environment; six authored fixtures; the expected exits; rejection of malformed JSON, duplicate keys, a non-finite constant, Markdown fences, and a missing file; acceptance of a UTF-8 BOM. The schema-valid wrong-owner fixture demonstrates the gap between declared rules and source truth.
Not tested: model inference, model-generated outputs, improved answer quality, accuracy, speed, a production agent, real approval enforcement, or other operating systems. This is a small-file teaching script, not a hardened service. Real deployments need input-size limits, controlled logs, permission checks, and controlled actions. Error messages can include input values.
Use synthetic or approved non-sensitive data. Review any changed schema and dependencies. This tutorial establishes a local validation exercise, not a security boundary or permission to process confidential records.
Keep the contract and evidence together.
Primary sources checked October 11, 2026: JSON Schema’s object, conditional, and string guides; the jsonschema validation documentation and 4.26.0 PyPI metadata; Python’s JSON decoder documentation. This is a practical tutorial, not a claim that the library launched today.
Complete exercise · Caption · Alt text · Posting ZIP
Manual social delivery: the original graphic, teaser caption, alt text, and article URL are prepared for the user to post. Nothing is scheduled or published to social media; no Instagram profile-link change is claimed.
