A two-record report says that R1 failed and R2 passed. Run .[] | .ok with jq -e, and stdout contains false followed by true. In the supplied jq 1.7 commissioning run, the process exits zero. The executable did not lose the earlier output, and Python did not hide a child failure. The expression produced two answers, while the exit-status option based its successful result on the last one.
For a CI maintainer, the operational question is narrower and stronger: does one complete evidence document contain exactly the expected records, with every required result represented by the JSON Boolean true? A printable success near the end cannot answer that question. Neither can a count of passing rows when the expected identities are missing or repeated.
This article uses only local synthetic JSON, jq 1.7, and a Python standard-library harness. Its fourteen cases retain the supplied input, exact argument list, stdout, stderr, and actual return code for each completed invocation. They contrast last-output behavior with a guarded reduction, then challenge empty evidence, wrong value types, missing fields, duplicate identities, multiple documents, and unfinished JSON. The resulting ACCEPT_BOUNDED decision covers this fixed R1/R2 contract, not a production release. The supplied run is identified separately from the documentation, and the complete source lets a rerun create its own evidence rather than inherit somebody else’s pass label.
Write the evidence population into the contract
Write the required population before choosing a jq expression. The candidate must be exactly one top-level JSON array. That array must contain exactly two objects: one whose id is the string R1, and one whose id is the string R2. Each object must have an ok member whose value is the literal JSON Boolean true. Record order is immaterial to admission. Missing records, extra records, and repeated identities are not equivalent to the expected population.
Additional object fields are harmless under this contract. A producer may attach a note or duration without changing admission, because the predicate checks the required fields rather than demanding an exact object shape. That tolerance does not extend to another top-level document or another record. Duplicate property names inside one JSON object are outside this parsed-input laboratory; repeated record identities inside the array are explicitly inside it.
These are authored requirements, not facts inferred from whatever arrived. EXPECTED = ["R1", "R2"] belongs to the consumer’s fixture. A report cannot qualify by announcing that the records it happens to contain are the only records that were required. Otherwise, a missing R2 could redefine the population instead of failing the check.
Separate three observations during review: which input was supplied, which values the expression produced, and how the process terminated. Preserving a return code correctly addresses only the third. A zero status can accurately summarize the wrong expression, just as a correct answer to “what was the last value?” can be inadequate for “did every expected record pass?” The repair must change the question, not merely improve status transport. Write that question into the code review so later refactoring has an explicit invariant to preserve.
Understand which output jq -e uses
The jq 1.7 exit-status contract makes -e depend on the last output value: zero for a value other than false or null, one for either of those values, and four when no valid result was produced. Usage, system, and compilation errors have separate documented status behavior. Do not read the option as an instruction to remember every earlier failed predicate.
The expression .[] | .ok asks jq to visit the array’s elements and emit each element’s ok value. It does not combine those outputs into one population verdict. In J1, there are two emitted values and one process return code. That mismatch is the center of the diagnostic: the stream contains a failed result, but the final value is sufficient for this invocation to exit zero.
Also distinguish a JSON Boolean from a value treated as true by jq. Under jq’s Boolean rules, only false and null are false-valued; numeric zero is not. Consequently, adding -e to a field extractor does not turn the extracted field into a strict Boolean assertion. Output type and output position both remain part of the expression’s contract.
Last output is not a universal quantifier
For this gate, one result must cover the entire expected population. The repair therefore changes both the expression and its input boundary. It checks one envelope, does not discard candidate rows before validation, and emits a single Boolean under the declared conditions. It does not ask the caller to reconstruct approval from several printed values.
The related lesson that pipeline status is a separate boundary concerns status propagation between processes. No shell pipeline participates here: Python invokes jq directly. The vertical bar inside the jq expression combines jq filters, not operating-system processes. J1 needs repair even when the caller captures jq’s zero status perfectly. Keeping those boundaries separate prevents a shell-level change from being mistaken for a semantic fix.
Run the fourteen-case local harness
Use a writable disposable directory with Python 3.13.5 and jq 1.7 on PATH for comparison with the supplied run. Record your platform too; matching two version strings does not reproduce the whole environment. There are no third-party Python packages, remote inputs, or account credentials. Save the complete source below as jq_gate_fixture.py, then run this command. The script creates a uniquely named directory beginning with jq-gate-run- beside itself and prints the path to its results.json. Retain that directory with the exact source used.
python jq_gate_fixture.pyThe supplied commissioning observation is PASS_14_CASES for fourteen main case IDs, using Python 3.13.5, platform Linux-6.18.44-x86_64-with-glibc2.41, and executable version jq-1.7. It began at 2026-10-16T06:18:23.406878+00:00 and ended at 2026-10-16T06:18:23.447236+00:00. These timestamps and outcome identify one synthetic local execution reported in the commissioning evidence. They are not performance measurements, a production deployment, or evidence about every supported version.
The source identity for that supplied run is SHA-256:
bed2244c2c879b2f3f8257073c8ce62f0a23980396faf4ac822ada18ecee46efPreserve the complete source, including its fixed CASES inventory. A changed file requires its own recorded digest and execution evidence; even a byte-level formatting change can change the digest. The harness calculates that identity from its own file, so a copied or edited rerun must use the value it actually records rather than paste the commissioning identity into a report.
"""Local jq process qualification; no shell pipeline and no remote input."""
import hashlib
import json
import platform
import subprocess
import tempfile
from datetime import datetime, timezone
from pathlib import Path
EXPECTED = ["R1", "R2"]
GATE = r'''
if length != 1 then false
else .[0] |
if type != "array" then false
elif length == 0 then false
elif all(.[]; if type == "object"
then (.ok == true and (.id | type == "string"))
else false end)
then (map(.id) | sort) == ($expected | sort)
else false end
end
'''
def records(a, b):
return json.dumps([{"id": "R1", "ok": a}, {"id": "R2", "ok": b}])
def invoke(text, expression, slurp=False):
args = ["jq", "-e", "--argjson", "expected", json.dumps(EXPECTED)]
if slurp:
args.append("--slurp")
args.append(expression)
result = subprocess.run(args, input=text, text=True, capture_output=True, check=False, timeout=5)
return {"argv": args, "returncode": result.returncode, "stdout": result.stdout, "stderr": result.stderr}
def require(ok, message):
if not ok:
raise RuntimeError(message)
CASES = [
("J1", records(False, True), ".[] | .ok", False, 0, "false\ntrue\n", "REPAIR"),
("J2", records(True, False), ".[] | .ok", False, 1, "true\nfalse\n", "CONTROL"),
("J3", records(True, True), GATE, True, 0, "true\n", "ACCEPT_BOUNDED"),
("J4", records(False, True), GATE, True, 1, "false\n", "REJECT_RECORD"),
("J5", "[]", "all(.[]; .ok == true)", False, 0, "true\n", "REPAIR_EMPTY_POLICY"),
("J6", "[]", GATE, True, 1, "false\n", "REJECT_EMPTY"),
("J7", records(0, True), GATE, True, 1, "false\n", "REJECT_TYPE"),
("J8", '[{"id":"R1"},{"id":"R2","ok":true}]', GATE, True, 1, "false\n", "REJECT_MISSING"),
("J9", records("true", True), GATE, True, 1, "false\n", "REJECT_TYPE"),
("J10", "", GATE, True, 1, "false\n", "REJECT_EMPTY_STREAM"),
("J11", records(False, True)+"\n"+records(True, True), GATE, True, 1, "false\n", "REJECT_ENVELOPE"),
("J12", records(True, True)+"\n{", GATE, True, None, "", "REJECT_PARSE_ERROR"),
("J13", '[{"id":"R1","ok":true},{"id":"R1","ok":true}]', GATE, True, 1, "false\n", "REJECT_IDENTITY"),
("J14", '[{"id":"R1","ok":true},{"id":"R3","ok":true}]', GATE, True, 1, "false\n", "REJECT_IDENTITY"),
]
def main():
directory = Path(tempfile.mkdtemp(prefix="jq-gate-run-", dir=Path(__file__).parent))
report = {"status": "FAIL", "fixture": "jq-gate-v1", "cases": [], "started_utc": datetime.now(timezone.utc).isoformat(), "python": platform.python_version(), "platform": platform.platform(), "source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
try:
report["jq"] = subprocess.run(["jq", "--version"], text=True, capture_output=True, check=True, timeout=5).stdout.strip()
for identity, text, expression, slurp, code, stdout, decision in CASES:
row = {"id": identity, "status": "FAIL", "input": text, "decision": decision}
report["cases"].append(row)
row.update(invoke(text, expression, slurp))
require((row["returncode"] != 0 if code is None else row["returncode"] == code), identity + ": wrong exit status")
require(row["stdout"] == stdout, identity + ": wrong output")
if identity == "J12":
require(bool(row["stderr"]), "parse diagnostic missing")
else:
require(not row["stderr"], identity + ": unexpected stderr")
if identity == "J7":
row["truthiness_control"] = invoke(text, "all(.[]; .ok)")
require(row["truthiness_control"]["returncode"] == 0, "zero truthiness control changed")
row["status"] = "PASS"
require(len(report["cases"]) == 14, "incomplete inventory")
report["status"] = "PASS_14_CASES"
except Exception as error:
report["error"] = {"type": type(error).__name__, "message": str(error)}
raise
finally:
report["finished_utc"] = datetime.now(timezone.utc).isoformat()
path = directory / "results.json"
path.write_text(json.dumps(report, indent=2, allow_nan=False)+"\n")
print(path)
if __name__ == "__main__":
main()The jq project release history places -e and reduction facilities in established releases; they are not a new launch associated with this article’s date. Its moving changelog is release context, not proof of the executable on PATH. The harness records jq --version, Python’s runtime version, and the platform string. It records versions but does not enforce equality with the commissioning environment, so review them before comparing results. The argument list records the name jq, not a resolved executable-path inventory.
The reference pages were accessed on October 16, 2026. The Python source is the 3.13 documentation branch, not a claim that the observed interpreter had the documentation site’s currently displayed maintenance version. Keep reference versions, access dates, source identity, and execution timestamps as separate evidence fields.
Preserve the executable boundary
The Python subprocess.run documentation supports this argument-list invocation with separate captured streams, check=False, and a timeout. Here, deliberate nonzero jq results remain inspectable completed processes rather than automatically becoming CalledProcessError. Version discovery uses check=True because failure to identify the required executable is a setup failure, not a planned rejected report. Each process call specifies a five-second timeout. Process creation can extend elapsed time beyond that interval, so this is not an end-to-end deadline or a byte-size limit.
The driver fails nonzero if an assertion fails or a required dependency is missing. Within its guarded execution block, it records an exception and attempts to write the report before re-raising. A timeout or failed process start may leave the affected row without a completed return-code and stream tuple. Do not invent those fields. With text=True, captured streams are decoded text, not byte-for-byte archives. Directory creation and final report writing still require a working writable location; the script cannot guarantee a result file when storage itself fails.
The non-array and non-object rejection branches, and tolerance for extra object fields, follow from the shown filter. They are not additional executed cases in this fourteen-case inventory. Distinguish those code-derived properties from the supplied observations rather than enlarging the run’s coverage claim.
PASS_14_CASES means the diagnostic assertions matched, including the deliberately flawed controls. It does not approve J1’s extractor or J5’s empty-set policy. J12 deliberately asserts only a nonzero code, empty stdout, and a present diagnostic; it does not require one universal parse-error number or message. J7’s nested truthiness control records both streams but asserts only its zero return code. Those differences in assertion strength are part of the fixture, not details to silently normalize.
Put the failed record before and after the success
J1 supplies records(False, True) to .[] | .ok without slurping. Its stdout is exactly false\ntrue\n, stderr is empty, and the expected exit status is zero. The first emitted result exposes the failed requirement. The final emitted result explains why this particular expression still reports process success. Preserve both lines in order: a summary that records only “exit 0” removes the evidence needed to diagnose the mismatch.
J2 uses records(True, False) with the same expression and options. It emits true\nfalse\n, keeps stderr empty, and exits one. The expression has not become a whole-population gate. Its final value has changed. Together, these controls show that a failed value can coexist with either status, depending on where that failure appears in the output sequence.
Be precise about what this driver varies. J1 and J2 reverse the Boolean sequence by changing the values assigned to R1 and R2. They do not physically reorder the same two record objects while keeping each identity’s result fixed: records() always writes R1 first. The pair demonstrates output-position sensitivity. The requirement that reordering a fixed candidate must not change admission follows from the authored population contract; this pair is not a separate empirical reorder test.
Both candidates contain a failure, so neither qualifies for acceptance. J2’s nonzero status is a useful negative control, not validation of the extractor’s design. A reviewer who checks only a final-failure example could mistakenly conclude that .[] | .ok already enforces the intended rule. J1 is necessary because it places a successful output after the failed one.
During repair, keep the process observation and the policy conclusion side by side. “jq returned zero” is true for J1. “Every required record passed” is false. Recording both statements avoids blaming a correctly observed return code and identifies the exact assumption that must change. Do not delete the earlier false from stdout or recast a passing diagnostic assertion as accepted CI evidence. The flaw is reproducible without introducing another process boundary.
Reduce a validated population to one Boolean
Read GATE from the outside inward. With --slurp, its initial input is the array of parsed top-level values. length != 1 rejects any population of documents other than exactly one. Only after that check does .[0] select the candidate document. The next branches reject a non-array value and an empty array before record validation begins.
For each candidate element, the inner conditional first requires an object. It then requires .ok == true and an id whose type is string. The explicit object branch avoids treating arbitrary array elements as valid records. The manual’s strict equality rule explains why equality to true enforces both the required type and value, rather than mere truthiness.
Once the record predicate succeeds, map(.id) | sort must equal the sorted independently supplied expected list. This comparison includes length and multiplicity. Combined with the per-record predicate, it establishes exactly R1 and R2, each once, with both required results true. GATE has no branch that returns an accepted subset of the original report.
J3 supplies both expected records with Boolean true and produces true\n with exit zero. J4 supplies the same failed-first candidate as J1 but produces only false\n with exit one. The changed result comes from the changed question, not a modified input. A rejection need not enumerate every later defect; this is an admission predicate, not a complete issue collector.
Keep the identity manifest independent
Sorting removes irrelevant record order without removing duplicates. Compare the two lists as multisets, not as deduplicated sets. Constructing the expected list from the candidate would make this test circular. Likewise, filtering out failed rows before applying all would redefine the inspected population. A selector can create a useful view, but that view is not evidence that every required original record passed. Admission must retain the population obligation even when only a single Boolean is returned.
Choose an explicit empty-input policy
J5 evaluates all(.[]; .ok == true) against []. It prints true\n and exits zero. The manual’s empty-array reduction rule explicitly gives all a true result on an empty array. That is coherent universal logic: there is no element that contradicts the predicate. It is not a jq bug, and changing the process wrapper cannot decide whether that empty population meets the declared CI evidence requirement.
Our contract already decided the policy: evidence for R1 and R2 is mandatory. J5 therefore receives the fixture label REPAIR_EMPTY_POLICY. Its predicate uses strict equality but omits the required population. This is a useful separation of concerns. Correct Boolean comparison alone does not establish that the relevant records exist.
J6 supplies the same empty array to GATE with slurping. The outer slurp array has one member, so the document-count check succeeds. The selected document itself has length zero, so the explicit empty-array branch returns false. Stdout is false\n, and jq exits one. The independent identity comparison would also prevent the required two-record population from being replaced by nothing; the early empty branch makes that policy visible.
J10 instead supplies an empty string as the entire input stream. Slurping produces an empty outer array, so the first branch rejects the absence of any document. This command emits false\n and exits one. Do not substitute the documented no-output status for this observed fixture expectation: this filter is invoked on the slurped empty collection and deliberately produces a Boolean result.
The operational outcome is REJECT_EMPTY for both absent evidence and an empty evidence array, while the raw fixture labels retain the distinction. Keep it available for diagnosis: an absent report and an intentionally written [] require different producer questions. Neither gets to satisfy the R1/R2 requirement through vacuous truth. An empty-input rule should be explicit before a job runs, not improvised after an empty report makes the reduction look successful.
Distinguish true from truthy evidence
J7 replaces R1’s Boolean with numeric zero. GATE emits false\n and exits one, producing the REJECT_TYPE case. Its nested control runs all(.[]; .ok) on the same text and records a zero return code. That control demonstrates why a bare truthiness reduction cannot implement this contract: jq treats the zero as true-valued, even though it is not the approved Boolean.
J9 instead supplies the string "true" for R1. It also fails strict admission. RFC 8259’s JSON value grammar, published in December 2017, distinguishes strings and numbers from the literal Boolean names. Quotes are therefore substantive evidence, not display decoration. A string that spells a favorable result is still a different JSON value from the one this consumer agreed to accept.
Do not import assumptions about zero or empty strings from another programming language. The relevant question is not whether a human reads a field as affirmative, or whether an application would coerce it favorably. It is whether the parsed value equals the exact JSON Boolean true. This choice gives producers a concrete serialization obligation and reviewers a stable oracle.
A missing value is not an approved false or true
An absent result is also not an invitation to invent a Boolean. Retain the original object so reviewers can distinguish a missing member from an explicit failed result. Both are rejected here, but one indicates absent evidence while the other records a negative answer. A single false output from the admission filter intentionally does not preserve that distinction by itself.
Avoid repairs that normalize "true", zero, or null into a claimed pass. Such conversions change the contract and can hide a producer defect behind consumer convenience. The driver’s Python values are only a convenient way to generate synthetic JSON; the gate evaluates the resulting JSON types. When reviewing J7, inspect the retained input as well as the nested control. A passing truthiness diagnostic is evidence against the loose predicate, not evidence that a numeric result is acceptable.
Reject missing fields and missing identities
J8 retains R1’s identity but omits its ok field. The manual’s object-field lookup rule returns null for an absent object member, so .ok == true fails. The record is rejected rather than silently removed. The fixture preserves the raw label REJECT_MISSING; operationally this belongs under REJECT_RECORD because required result evidence is absent. It does not establish that the underlying check ran and failed.
J13 has two approved-looking records whose identities are both R1. Their Boolean results pass the local value requirement, but the sorted identity list is ["R1", "R1"], not ["R1", "R2"]. The duplicate cannot stand in for the missing R2. A count of two passing rows would miss that difference. A membership-only check could miss it as well if it never establishes which required identity is absent.
J14 substitutes R3 for R2 while preserving two Boolean true values. Its list also differs from the manifest, so the result is REJECT_IDENTITY. R3 is a deliberate unexpected-identity sentinel, not a third expected check. The required population remains R1/R2. Together, J13 and J14 separate duplicate substitution from substitution by an unexpected identity.
Do not insert unique into the comparison as an automatic cleanup step. Removing repeated identities changes evidence before validation. Nor should a candidate’s list become the source of $expected. The manifest must be authored independently and reviewed when the expected population changes. Extra descriptive fields are allowed; extra or substituted record identities are not.
This is a deliberately small schema contract, not a full JSON Schema tutorial or proof of where the report originated. The separate topic of verification versus release approval concerns a different decision. Here, string identities establish which synthetic records are represented, not a signer, repository, artifact, or approval. Keep the name “identity” tied to that limited meaning so a strict row check is not overstated as a broader trust guarantee.
Keep one document distinct from a jq input stream
J11 concatenates a failed-first array, a newline, and an all-true array. jq can parse these as two input values. The manual’s input and slurp contract explains that ordinary invocation processes input values separately, while --slurp collects them into one array for one filter invocation. A per-document success does not establish that the whole supplied input contained exactly one qualifying document.
Under GATE, J11 becomes an outer array with two members and is rejected before either document is treated as the accepted envelope. Stdout is false\n, and the exit status is one. There is no rule allowing the later document to supersede the earlier one. That would be a different input contract requiring its own explicit design, not a formatting repair to this one.
RFC 8259’s single-text grammar describes one serialized value with surrounding whitespace. jq’s ability to consume multiple values is an input-stream behavior, not a claim that two concatenated arrays are one ordinary JSON text. This distinction also separates J11 from malformed input: its values are parseable, but their count violates the envelope requirement.
The broader idea of global reduction versus partial output offers a separate scope comparison. This laboratory has no remote listing or page traversal: the entire finite input is supplied locally. The relevant scope here is the number of top-level values and the complete population inside the one permitted array.
Slurp is a bounded-file design choice
Slurping reads the input into memory before this filter evaluates the collected values. Use this design only for small, bounded CI evidence files. The fixture is bounded because its input strings are fixed in the source; it does not implement an arbitrary-file size admission limit. The five-second subprocess timeout is not a substitute for one.
Before adapting the design to larger evidence, define a separate producer and file-size boundary. No memory benchmark, unbounded-input guarantee, or alternative large-log design is established here. Preserve that limitation alongside the predicate: one complete result is useful only when the way the input is acquired also matches the declared scope.
Treat parser completion as part of the gate
J12 appends a newline and an unfinished object opener to an otherwise acceptable array. Because GATE is slurped, this invocation does not evaluate the successfully parsed prefix as a completed accepted envelope. The fixture requires empty stdout, a nonzero exit status, and nonempty stderr. Its outcome is REJECT_PARSE_ERROR, not a rejected Boolean result printed by GATE.
The supplied commissioning summary reports that these assertions passed, without specifying J12’s exact numeric status or diagnostic wording. The table therefore records the tested expectation: a nonzero status and a present diagnostic. The driver retains the actual code and message in results.json. Preserve those fields during a rerun instead of replacing them with another version’s preferred wording.
In particular, an earlier printable true from some different non-slurped command would not establish successful parser completion. Completion belongs to the full process observation. Do not search stdout for a favorable token and disregard the return code or diagnostic stream. The record-result predicate and the parser boundary must both finish successfully before acceptance.
Use the documented jq error-status distinctions to keep ordinary false-result rejection separate from invocation, compilation, and input failures. A return code is not a complete causal explanation on its own. Log the expression, input, and stderr, then route the failure according to what actually happened. An executable that could not start is different again: Python has an execution exception rather than an ordinary jq Boolean verdict.
Do not introduce catch true or an early halt as a way to make the gate green. The intended design has no favorable fallback for an incomplete decision. A repair must preserve failure visibility, not erase it. Treat a missing or inconsistent process record as unresolved execution evidence and withhold admission until the discrepancy is understood; do not relabel every nonzero result as a failed R1 test.
Build a compact evidence reconciliation table
This table reconciles the fixed inventory with its asserted expectations. S means .[] | .ok; A means all(.[]; .ok == true); T means all(.[]; .ok); G means GATE with --slurp. Stdout uses literal \n notation. The decision column preserves the driver’s raw labels rather than pretending jq emits those names.
Case | Input category | Filter | Stdout | Exit | Fixture decision |
J1 | R1 false; R2 true | S | false\ntrue\n | 0 | REPAIR |
J2 | R1 true; R2 false | S | true\nfalse\n | 1 | CONTROL |
J3 | Both expected; true | G | true\n | 0 | ACCEPT_BOUNDED |
J4 | R1 false; R2 true | G | false\n | 1 | REJECT_RECORD |
J5 | Empty array | A | true\n | 0 | REPAIR_EMPTY_POLICY |
J6 | Empty array | G | false\n | 1 | REJECT_EMPTY |
J7 | R1 zero; R2 true | G | false\n | 1 | REJECT_TYPE |
J8 | R1 lacks ok | G | false\n | 1 | REJECT_MISSING |
J9 | R1 string "true" | G | false\n | 1 | REJECT_TYPE |
J10 | No input value | G | false\n | 1 | REJECT_EMPTY_STREAM |
J11 | Two complete arrays | G | false\n | 1 | REJECT_ENVELOPE |
J12 | Valid array plus { | G | Empty | Nonzero | REJECT_PARSE_ERROR |
J13 | R1 repeated; true | G | false\n | 1 | REJECT_IDENTITY |
J14 | R3 replaces R2 | G | false\n | 1 | REJECT_IDENTITY |
J7 subcontrol | Same numeric-zero input | T | Captured; not asserted | 0 | Truthiness control |
For J1–J14, stderr must be empty except in J12, where a diagnostic is required. J7’s nested control captures stderr without asserting it. Its logically expected stdout is true\n, but the source asserts only its zero status; the table does not upgrade that to an exact-output test.
Map CONTROL and REPAIR_EMPTY_POLICY to repair evidence, REJECT_MISSING to REJECT_RECORD, and REJECT_EMPTY_STREAM to REJECT_EMPTY. These mappings explain operational vocabulary without editing CASES. J1 through J14 must each appear once in a completed report; the nested control is not a fifteenth main case. Compare identities, outputs, and diagnostics, not merely the final PASS_14_CASES string. Missing rows, repeated rows, and unexpected rows require investigation rather than silent deletion.
Repair a gate without rewriting the evidence
Retain the original CI report and the gate revision that interpreted it before changing either. For J1, the useful record is the failed-first input together with .[] | .ok, its ordered stdout, its empty stderr, and its zero return code. Those observations explain the defect. Replacing the failed value with true would destroy the counterexample rather than repair the consumer.
Apply the corrected predicate to the retained input under the declared envelope and manifest. J4 already demonstrates this comparison for the supplied synthetic data: its input matches J1’s, but GATE returns a single false and exits one. Keep both observations. That before-and-after relationship is more informative than showing only a newly green report generated after the filter changed.
Treat a producer rerun as new evidence, not a rewrite of the earlier file. A later R1 success may legitimately produce a new candidate that qualifies, but it does not make the earlier failed result disappear. Retain the reason for the rerun, the current manifest, and the actual source identity with its outputs. Recalculate the digest after any source edit, even one that appears cosmetically harmless.
Review the caller after fixing the predicate, but do not substitute caller changes for the semantic repair. The related topic of shell context in build recipes addresses a different command-boundary problem. It is not exercised by this Python-to-jq fixture and does not explain away jq’s documented last-output result.
A passing filter is not release authorization
The corrected filter establishes one narrow fact about the JSON candidate it received. It does not establish who produced that candidate, whether the underlying checks were honest, or which artifact a release owner intends to promote. No signature, repository identity, or independent approval is verified in this laboratory.
For that reason, use ACCEPT_BOUNDED rather than a sweeping “safe to release” label. The repaired implementation and a particular accepted input are also different things: a valid gate can reject a bad report. A successful fixture qualification shows that the specified diagnostic assertions matched on that run, including rejection cases; it is not a substitute for the separate decision to act on real evidence.
Assign acceptance, rejection and hold ownership
CI maintainers own the executable invocation, the filter revision, and preservation of process evidence. Their review should establish which expected manifest was supplied, whether the invocation completed, and whether its output matched the agreed single-Boolean protocol. Test producers own stable record identities and the serialization of required results. Release owners decide the consequence of missing or rejected evidence without silently redefining what the predicate accepted.
ACCEPT_BOUNDED requires a completed parse, exactly one expected envelope, the exact R1/R2 identity multiset, and Boolean true for every required result. For the corrected invocation, the accepted protocol is true\n, status zero, and no diagnostic. REPAIR identifies an inadequate implementation or policy control, not permission to pass the candidate. Keep those meanings separate so a maintainer does not confuse improving the gate with improving the underlying test result.
REJECT_RECORD covers an explicit failed requirement or absent required result evidence, with the retained input distinguishing those causes. REJECT_TYPE describes a non-Boolean value such as J7’s zero or J9’s string. REJECT_IDENTITY covers duplicate, missing, substituted, or extra record identities. REJECT_EMPTY covers the absence of required evidence, while REJECT_ENVELOPE covers a document boundary that does not meet the single-array contract. REJECT_PARSE_ERROR preserves failure to parse the complete candidate.
These labels are diagnostic interpretations, not separate exit codes emitted by GATE. Several rejected inputs produce the same false\n and status one. CASES assigns known labels to synthetic inputs; the filter does not implement a general rejection classifier or enumerate every simultaneous defect. In particular, J14’s missing R2 and unexpected R3 are aspects of one identity mismatch, not independently proven process errors.
Use HOLD as an operational disposition when execution evidence is incomplete, the environment is unexplained, or the schema and expected population are unresolved. It is not another Boolean verdict, and it must not become an implicit pass. Assign an owner to resolve the missing evidence before retrying. A timeout and a failed record may both prevent admission, but they demand different corrective work.
The practice of retaining reviewable CI evidence helps make that handoff concrete. Require the original input, manifest revision, invocation, outputs, and actual status rather than an isolated green badge. When a producer changes the schema or required population, revise and review the consumer manifest deliberately. Do not let a new report shape authorize its own acceptance rules, and do not describe fourteen synthetic cases as complete coverage of future schema changes.
Develop stronger CI review habits
A useful review starts by asking what a successful exit proves. For this gate, the answer must name the complete input boundary, the expected identities, and the exact Boolean value. Then ask which retained counterexample would expose a weaker implementation. J1 challenges last-output approval, J5 challenges empty-set policy, and J13 challenges duplicate substitution. Their diagnostic success keeps the mistakes visible; it does not approve them.
Keep the expression, manifest, and evidence protocol reviewable together. A one-line command is not automatically a one-decision contract, and a long list of true outputs does not prove the required population was present. The practical habit is to connect every acceptance claim to its retained input and completed process observation.
For structured practice in the surrounding foundations, explore Refonte Learning’s DevOps Engineering programme, which lists Linux fundamentals and scripting, Git/GitHub, and CI/CD. Those foundations are relevant to reviewing automation; the programme page does not establish coverage of this particular jq fixture.
