From f075ee0ed1fa79d971170868ec81b26e3a0f60a4 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:14:30 +0530 Subject: [PATCH 1/3] docs: document strict JSON consumer validation --- docs/index.md | 2 + docs/strict-json-consumer.md | 79 +++++++++++++++++++++++++ mkdocs.yml | 1 + tests/test_strict_json_consumer_docs.py | 25 ++++++++ 4 files changed, 107 insertions(+) create mode 100644 docs/strict-json-consumer.md create mode 100644 tests/test_strict_json_consumer_docs.py diff --git a/docs/index.md b/docs/index.md index 741efeb..7152c1d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,6 +75,8 @@ application. cancellation rules. - Review the [JSON contracts](json-contracts.md) and [output contracts](output-contracts.md) before building automation around command output. +- Use the [strict JSON consumer guide](strict-json-consumer.md) to validate + envelope and NDJSON records with a cross-language parser. ## Design principles diff --git a/docs/strict-json-consumer.md b/docs/strict-json-consumer.md new file mode 100644 index 0000000..6ad347c --- /dev/null +++ b/docs/strict-json-consumer.md @@ -0,0 +1,79 @@ +# Strict JSON consumer validation + +Use a strict cross-language parser when a downstream application consumes +`base-cli` machine output. Python's `json` module is useful for local tooling, +but Node's built-in `JSON.parse` is a closer model for a consumer that must +reject malformed JSON before using the payload. + +## Validate one JSON envelope + +The repository's success fixture is a representative `base-cli.output` JSON +document. From the repository root, validate its syntax and stable top-level +contract with Node: + +```bash +node -e ' +const fs = require("node:fs"); +const payload = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); +const required = ["schema_version", "schema", "code", "type", "message", "details", "run_id"]; +for (const key of required) { + if (!(key in payload)) throw new Error(`missing ${key}`); +} +if (payload.schema_version !== 1 || payload.schema !== "base-cli.output") { + throw new Error("unexpected base-cli output contract"); +} +console.log(`${payload.schema} ${payload.code}`); +' tests/fixtures/contracts/output-success.json +``` + +The expected output is `base-cli.output ok`. `JSON.parse` validates syntax and +the small assertions validate the envelope identity; it does not make +undocumented nested fields stable. Consumers should treat `details.stdout` as +the captured string described by the [JSON contract](json-contracts.md), and +preserve the process exit status and stderr boundary. + +## Validate NDJSON one record at a time + +NDJSON is a stream of independent records, not one JSON envelope. Parse each +non-empty line separately and reject the stream if any line is malformed: + +```bash +node -e ' +const fs = require("node:fs"); +const lines = fs.readFileSync(process.argv[1], "utf8").split(/\r?\n/).filter(Boolean); +for (const [index, line] of lines.entries()) { + const record = JSON.parse(line); + if (record.schema_version !== 1 || record.schema !== "base-cli.record") { + throw new Error(`unexpected record contract on line ${index + 1}`); + } +} +console.log(`validated ${lines.length} record(s)`); +' tests/fixtures/contracts/ndjson-record.json +``` + +The expected output is `validated 1 record(s)`. Do not concatenate NDJSON lines +and pass them to one `JSON.parse` call; consume one record per line and keep +diagnostics on stderr. + +## Syntax failure versus contract failure + +Consumers should distinguish two failures: + +- malformed JSON: `JSON.parse` throws a `SyntaxError`; reject the complete + payload or record stream and report the producer's exit status; +- valid JSON with an invalid shape: parsing succeeds, but schema validation + must reject it. The repository's + [`scripts/validate_contract_fixtures.mjs`](../scripts/validate_contract_fixtures.mjs) + checks both the valid fixtures and the intentionally invalid + `tests/fixtures/contracts/invalid-output-extra-field.json` fixture. + +Run the repository-owned cross-language contract check with: + +```bash +node scripts/validate_contract_fixtures.mjs +``` + +The schemas and fixtures are the source of truth. Do not add a new parser or +redefine the wire contract in an adopter guide; link the exact contract version +and record the `base-cli` release used for validation. Never place secrets or +private paths in fixtures or public failure reports. diff --git a/mkdocs.yml b/mkdocs.yml index d86d87d..021ff7a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -50,6 +50,7 @@ nav: - argparse migration: migration-argparse.md - Output contracts: output-contracts.md - JSON contracts: json-contracts.md + - Strict JSON consumer: strict-json-consumer.md - JSON Schema artifacts: schemas.md - Typed user configuration: user-config-typing.md - Entry-point extensions: extensions.md diff --git a/tests/test_strict_json_consumer_docs.py b/tests/test_strict_json_consumer_docs.py new file mode 100644 index 0000000..6e613a1 --- /dev/null +++ b/tests/test_strict_json_consumer_docs.py @@ -0,0 +1,25 @@ +from pathlib import Path + +ROOT = Path(__file__).parents[1] +DOC = ROOT / "docs" / "strict-json-consumer.md" +INDEX = ROOT / "docs" / "index.md" + + +def test_strict_json_consumer_guide_is_linked_from_the_documentation_index() -> None: + assert "strict-json-consumer.md" in INDEX.read_text(encoding="utf-8") + + +def test_strict_json_consumer_guide_covers_json_and_ndjson_contract_boundaries() -> None: + text = DOC.read_text(encoding="utf-8") + + for required in ( + "JSON.parse", + "base-cli.output", + "base-cli.record", + "output-success.json", + "ndjson-record.json", + "invalid-output-extra-field.json", + "validate_contract_fixtures.mjs", + "stderr", + ): + assert required in text From 3e77c21783915e4063078c981375deb06cda43de Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:23:43 +0530 Subject: [PATCH 2/3] docs: register strict JSON guide in doc validation --- scripts/validate_docs.py | 1 + tests/test_strict_json_consumer_docs.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/scripts/validate_docs.py b/scripts/validate_docs.py index 48f98d9..da960f5 100644 --- a/scripts/validate_docs.py +++ b/scripts/validate_docs.py @@ -41,6 +41,7 @@ "security-review.md", "security-threat-model.md", "schemas.md", + "strict-json-consumer.md", "typer-adapter.md", "user-config-typing.md", } diff --git a/tests/test_strict_json_consumer_docs.py b/tests/test_strict_json_consumer_docs.py index 6e613a1..2afaaf1 100644 --- a/tests/test_strict_json_consumer_docs.py +++ b/tests/test_strict_json_consumer_docs.py @@ -3,10 +3,12 @@ ROOT = Path(__file__).parents[1] DOC = ROOT / "docs" / "strict-json-consumer.md" INDEX = ROOT / "docs" / "index.md" +VALIDATOR = ROOT / "scripts" / "validate_docs.py" def test_strict_json_consumer_guide_is_linked_from_the_documentation_index() -> None: assert "strict-json-consumer.md" in INDEX.read_text(encoding="utf-8") + assert "strict-json-consumer.md" in VALIDATOR.read_text(encoding="utf-8") def test_strict_json_consumer_guide_covers_json_and_ndjson_contract_boundaries() -> None: From 7251e0bc7be96df88ec176f574a03646822c701f Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:52:17 +0530 Subject: [PATCH 3/3] docs: keep validator reference compatible with MkDocs --- docs/strict-json-consumer.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strict-json-consumer.md b/docs/strict-json-consumer.md index 6ad347c..1a3e619 100644 --- a/docs/strict-json-consumer.md +++ b/docs/strict-json-consumer.md @@ -63,8 +63,8 @@ Consumers should distinguish two failures: payload or record stream and report the producer's exit status; - valid JSON with an invalid shape: parsing succeeds, but schema validation must reject it. The repository's - [`scripts/validate_contract_fixtures.mjs`](../scripts/validate_contract_fixtures.mjs) - checks both the valid fixtures and the intentionally invalid + repository-owned `scripts/validate_contract_fixtures.mjs` checks both the + valid fixtures and the intentionally invalid `tests/fixtures/contracts/invalid-output-extra-field.json` fixture. Run the repository-owned cross-language contract check with: