diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..745fcdc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,22 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm run lint + - run: npm test + - run: npm run conformance diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9b0171e..b4cdc3d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -4,6 +4,12 @@ generic input → Decision Evidence Protocol → generic output ``` +```text +packet-v1 ────────────────┐ + ├→ independent validator → structured result +optional source bytes ───┘ +``` + ## Ports - Input adapter: translates a host’s observable state into the generic contract. @@ -14,3 +20,8 @@ generic input → Decision Evidence Protocol → generic output ## Independence Host-specific adapters are optional and removable. Disabling the project should return the host to its prior behavior. No core module may import Taslos Tasks internals. + +The Context Firewall profile is a separately implemented classifier and receipt +auditor. Repository-local tests consume checked-in vectors. The optional +cross-repository proof dynamically imports an exact read-only producer checkout +only from a development tool, never from the package runtime or normal CI. diff --git a/CHANGELOG.md b/CHANGELOG.md index f490adb..cebb0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.2.0 - 2026-08-25 + +- Added an independent Context Firewall packet-v1 validator and stable API. +- Added machine-readable receipt-only and source-backed CLI validation. +- Added 24 public-safe conformance vectors with tamper, accounting, escalation, + version, hash, provenance, and numeric boundary failures. +- Added exact-revision cross-repository interoperability verification without a + runtime dependency. +- Documented structural versus cryptographic verification, source suppression, + raw-evidence limitations, and the EXP-001 boundary. + ## 0.1.0 - 2026-08-25 - Published initial theory, falsifiable specification, benchmark plan, architecture, and provenance. diff --git a/README.md b/README.md index e837670..d310ff7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,15 @@ A small structured envelope can improve interoperability and reduce context whil ## Mechanism -Emit status, exit state, changed entities, warnings/errors, test counts, repository revisions, duration, verification, uncertainty, provenance, artifacts, and an explicit raw-output escalation reason only when applicable. +Emit status, exit state, changed entities, warnings/errors, test counts, +repository revisions, duration, verification, uncertainty, provenance, +artifacts, and an explicit raw-output escalation reason only when applicable. + +The reference implementation also independently validates Context Firewall +`opsle.context-firewall.evidence-packet/v1` packets. It does not import or call +Context Firewall. With source bytes, it recomputes hashes, reclassifies the +documented TAP subset, verifies retained line locators, and derives suppression +accounting independently. ## Why it matters @@ -22,17 +30,109 @@ The Opsle thesis asks: **What if we stopped using intelligence for work that doe A universal event schema, embedding raw logs by default, or expanding fields without demonstrated decision value. +## Public API + +```js +import { + validateContextFirewallPacket, +} from '@opsle/decision-evidence-protocol'; + +const result = validateContextFirewallPacket( + packet, + { + packetBytes, + sourceInput, + }, +); +``` + +`sourceInput` is optional and uses Context Firewall's public input shape. A +caller that has only stream bytes can instead supply `sourceStreams` as +`{ name, bytes }` entries. Supplying neither validates the receipt internally +and returns `VALID_WITH_UNVERIFIED_SOURCE`; it never reports the input hash as +independently verified. + +The structured result includes: + +- `classification`: `VALID`, `VALID_WITH_UNVERIFIED_SOURCE`, + `STRUCTURALLY_INVALID`, `INTERNALLY_INCONSISTENT`, or + `CRYPTOGRAPHIC_MISMATCH`; +- `sufficiency`: `SUFFICIENT`, `NEEDS_RAW_EVIDENCE`, or `INVALID`; +- `verification`: per-claim states for configuration, source, semantic payload, + retained line hashes, canonical packet bytes, measurements, and accounting; +- `raw_evidence`: suppression, preservation, destruction, and caller-locator + claim state, with locator verification explicitly `CALLER_CLAIM_ONLY`; and +- `violations`: stable path-specific failure objects. + +`NEEDS_RAW_EVIDENCE` is valid protocol output but +`evidence_sufficient` is always false. + +## CLI + +Validate canonical packet bytes from stdin: + +```bash +node ./bin/decision-evidence.js \ + validate-context-firewall +``` + +Validate a packet and its source input: + +```bash +node ./bin/decision-evidence.js \ + validate-context-firewall \ + --packet packet.json \ + --source-input source.json +``` + +Output is canonical machine-readable JSON. Exit code 0 means a valid receipt, +including valid-but-insufficient escalation receipts; callers must inspect +`sufficiency`. Exit code 1 means validation failure. Invocation or JSON errors +use exit code 2. + +## Verification + +```bash +npm run lint +npm test +npm run conformance +``` + +The self-contained conformance corpus contains 24 public-safe vectors: 9 valid +producer packets and 15 intentional structural, consistency, boundary, and +cryptographic failures. See +[the packet-v1 profile](docs/context-firewall-packet-v1.md). + +The separately run cross-repository compatibility proof is: + +```bash +node \ + tools/verify-context-firewall-interop.js \ + ../context-firewall +``` + +It requires the exact documented read-only Context Firewall revision and does +not create a runtime or CI dependency on that repository. + ## Current maturity -**PROTOTYPE** under the [Opsle maturity model](https://github.com/opsle/research/blob/main/MATURITY.md). +The authoritative lifecycle stage is recorded by +[Opsle Research](https://github.com/opsle/research). The packet-v1 validator, +automated failure tests, conformance vectors, and exact-revision interoperability +proof establish a narrow verification claim. They do not establish comparative +benefit, benchmark readiness, or model correctness. ## Existing evidence -Structured provider results and operational metrics show feasibility inside one system. Cross-tool adequacy is unverified. +The generic envelope validator remains available. Context Firewall packet-v1 +interoperability is now executable and deterministic at the revisions recorded +in the authoritative Opsle registry. ## Evidence still missing -Adapter trials across test, Git, build, lint, typecheck, shell, and deployment tools; versioning and conformance rules. +Other tool classes, independent implementations beyond Context Firewall, +measured decision adequacy, comparative benchmarks, and replication remain +missing. ## Benchmark strategy @@ -61,7 +161,18 @@ A small dependency-free reference prototype is included for falsification and in ## Known limitations -Adapter trials across test, Git, build, lint, typecheck, shell, and deployment tools; versioning and conformance rules. +- A syntactically valid source hash is not independently verified without + supplied source bytes. +- `raw_evidence.reference` is caller-owned. Validation proves only whether a + nonempty reference was declared, not whether its target exists, is immutable, + is available, or contains the claimed bytes. +- Packet v1 hashes canonical `decision_evidence`; it has no self-referential + whole-packet hash. The validator instead checks exact canonical packet bytes + when supplied and always checks the fixed-point `reduced_bytes` measurement. +- The strict TAP-subset classifier is not arbitrary TAP or general log support. +- A valid receipt does not prove that a model will make a correct decision from + reduced evidence. That remains the planned EXP-001 question, and no model or + provider experiment is run here. ## License diff --git a/SPEC.md b/SPEC.md index 549cee0..3c0d232 100644 --- a/SPEC.md +++ b/SPEC.md @@ -2,6 +2,9 @@ Status: experimental prototype contract. +Versioned Context Firewall profile: +`opsle.decision-evidence.context-firewall-validation/v1`. + ## Compatibility boundary The primitive accepts generic structured input and emits generic structured output. It must not require a Taslos Tasks database, worker, scheduler, package, runtime path, or private service. @@ -29,6 +32,10 @@ The primitive accepts generic structured input and emits generic structured outp - Raw output is referenced, not embedded by default. - Protocol growth requires measured decision value. +The Context Firewall packet-v1 validation profile additionally enforces the +normative invariants in +[`docs/context-firewall-packet-v1.md`](docs/context-firewall-packet-v1.md). + ## Failure behavior Missing required authority or evidence fails closed. Unsupported optional data remains explicit and does not silently widen behavior. Implementations must document idempotency, crash consistency, and raw-evidence escalation. @@ -36,3 +43,7 @@ Missing required authority or evidence fails closed. Unsupported optional data r ## Versioning Breaking semantic changes require a new protocol version. New optional fields require evidence that they affect a real decision. + +The Context Firewall profile supports only evidence packet v1, receipt version +1, test-run input v1, reducer 0.2.0, and TAP-subset policy v1. Unsupported +versions are rejected explicitly; they are not interpreted as compatible. diff --git a/bin/decision-evidence.js b/bin/decision-evidence.js new file mode 100755 index 0000000..6910f8e --- /dev/null +++ b/bin/decision-evidence.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +import { readFile } from 'node:fs/promises'; +import process from 'node:process'; + +import { + canonicalJson, + validateContextFirewallPacket, +} from '../src/context-firewall-v1.js'; +import { runContextFirewallConformance } from '../src/conformance.js'; + +function usage() { + return [ + 'usage: decision-evidence validate-context-firewall [--packet PATH|-] [--source-input PATH]', + ' decision-evidence conformance', + '', + ].join('\n'); +} + +async function readBytes(path) { + if (path !== '-') return readFile(path); + const chunks = []; + for await (const chunk of process.stdin) chunks.push(chunk); + return Buffer.concat(chunks); +} + +function parseValidateArgs(args) { + let packet = '-'; + let sourceInput = null; + for (let index = 0; index < args.length; index += 1) { + if (args[index] === '--packet' && args[index + 1]) packet = args[++index]; + else if (args[index] === '--source-input' && args[index + 1]) sourceInput = args[++index]; + else throw new TypeError(`unknown or incomplete argument: ${args[index]}`); + } + return { packet, sourceInput }; +} + +async function parseJsonFile(path, label) { + const bytes = await readBytes(path); + try { + return { bytes, value: JSON.parse(bytes.toString('utf8')) }; + } catch { + throw new TypeError(`${label} must be valid JSON`); + } +} + +async function main() { + const [command, ...args] = process.argv.slice(2); + if (command === 'conformance') { + if (args.length > 0) throw new TypeError('conformance accepts no arguments'); + const report = await runContextFirewallConformance(); + process.stdout.write(`${canonicalJson(report)}\n`); + process.exitCode = report.conformance === 'PASS' ? 0 : 1; + return; + } + if (command !== 'validate-context-firewall') { + process.stderr.write(usage()); + process.exitCode = 2; + return; + } + const options = parseValidateArgs(args); + const packet = await parseJsonFile(options.packet, 'packet'); + const sourceInput = options.sourceInput === null + ? undefined + : (await parseJsonFile(options.sourceInput, 'source input')).value; + const result = validateContextFirewallPacket(packet.value, { + packetBytes: packet.bytes, + sourceInput, + }); + process.stdout.write(`${canonicalJson(result)}\n`); + process.exitCode = result.valid ? 0 : 1; +} + +main().catch((error) => { + process.stderr.write(`${canonicalJson({ code: 'INVALID_INVOCATION', message: error.message })}\n`); + process.exitCode = 2; +}); diff --git a/docs/README.md b/docs/README.md index 611772f..a96bb30 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,3 +1,8 @@ # Documentation -This directory is reserved for glossary, threat model, adapter guidance, and experiment interpretation that would obscure the core specification. +This directory holds profile-specific normative guidance and verification +boundaries that would obscure the generic specification. + +- [`context-firewall-packet-v1.md`](context-firewall-packet-v1.md) defines the + supported producer contract, enforced invariants, sufficiency semantics, and + known limits. diff --git a/docs/context-firewall-packet-v1.md b/docs/context-firewall-packet-v1.md new file mode 100644 index 0000000..b5ef6fc --- /dev/null +++ b/docs/context-firewall-packet-v1.md @@ -0,0 +1,179 @@ +# Context Firewall evidence packet v1 validation profile + +Status: normative experimental conformance profile. + +Profile version: +`opsle.decision-evidence.context-firewall-validation/v1`. + +Producer contract: +`opsle.context-firewall.evidence-packet/v1` at Context Firewall revision +`dd34bd9f681314761f1ca87f339648bf611811f3`. + +## What a validated receipt establishes + +Receipt-only validation establishes that the packet uses the supported version, +has the required typed fields, is internally consistent, has valid hash syntax, +has a correct canonical configuration identity and semantic-payload hash, has +correct retained/suppressed arithmetic, has a fixed-point canonical output byte +measurement, and cannot present encoded escalation state as sufficient evidence. + +When exact source bytes are supplied, validation additionally establishes that: + +1. the framed stdout/stderr SHA-256 input identity matches; +2. per-stream and total byte counts match; +3. every retained or hash-addressed source locator resolves to the supplied + line, category, byte count, and digest; +4. the strict TAP-subset classification, aggregate counts, failure identities, + ambiguity state, and reason codes match an independent implementation; and +5. retained and suppressed source event counts and suppression categories match + independent classification. + +When the original packet bytes are supplied, validation also proves that the +bytes are the producer's canonical lexical-key JSON with exactly one final +newline. + +## What it does not establish + +A receipt-only result cannot prove the input hash or source-derived +classification. It returns `VALID_WITH_UNVERIFIED_SOURCE`, and the input-hash and +source-accounting claim states remain `NOT_SUPPLIED`. + +The raw-evidence locator is caller-owned. `CALLER_REFERENCE_SUPPLIED` means only +that the producer received a nonempty string. It does not establish existence, +availability, immutability, access authorization, retention duration, or byte +identity of an external artifact. `PRESERVATION_UNCONFIRMED` means no locator was +supplied. Neither state says the reducer destroyed evidence; packet v1 requires +`destroyed_by_reducer: false`. + +Packet v1 contains a SHA-256 identity for canonical `decision_evidence`, not for +the entire packet. A whole-packet hash would be self-referential. The validator +checks canonical packet bytes directly when supplied and checks the canonical +fixed-point `reduced_bytes` measurement in every validation. + +Most importantly, receipt validation does not prove that reduced evidence is +adequate for a model to make a correct decision. That is a comparative EXP-001 +question requiring frozen tasks, a correctness oracle, model/provider +configuration, experimental arms, and measured runs. None are part of this +profile. + +## Structural and identity invariants + +The validator rejects unsupported or malformed values rather than applying +loose compatibility: + +- packet protocol is exactly evidence packet v1; +- receipt version is exactly 1; +- source protocol is exactly test-run input v1; +- reducer identity is exactly + `@opsle/context-firewall/test-output` version `0.2.0`; +- policy revision is exactly `tap-subset-policy/v1`; +- source, run, operation, and raw-reference identities are either nonempty + strings or `null`, according to packet v1; +- streams are one or two unique `stdout`/`stderr` entries in canonical order; +- SHA-256 claims are lowercase `sha256:` plus 64 hexadecimal characters; +- counts and byte measurements are nonnegative JavaScript safe integers; +- payload ceilings are positive safe integers or `null`; and +- line locators use declared streams and positive safe integer line numbers. + +No URL scheme is imposed on `raw_evidence.reference`; Context Firewall accepts +any nonempty caller-owned locator string. Empty or non-string values are +malformed. + +## Cryptographic and canonical invariants + +The validator always independently recomputes: + +- configuration identity from canonical ceiling, policy, and reducer version; +- semantic payload identity from canonical `decision_evidence`; +- each retained line digest when its text is present; and +- canonical packet length, including its final newline. + +With source bytes, it also recomputes input framing and SHA-256, plus line +digests and byte counts for hash-only line references. Without those bytes, +input and hash-only source claims are explicitly partial or not supplied. + +## Evidence and suppression accounting invariants + +The validator enforces: + +- `retained_evidence_count == retained.event_count`; +- `suppressed_evidence_count == suppressed.event_count`; +- retained plus suppressed equals `original_event_count`; +- suppression category counts sum to `suppressed.event_count`; +- retained event count equals the number of model-visible source-line texts; +- retained categories equal the four derived categories plus the unique + categories of retained source-line texts; +- suppressed-from-context is true exactly when suppressed event count is + nonzero; +- stream byte counts sum to `original_bytes`; and +- with source supplied, all event totals and categories match independent + source classification. + +No equation equates test counts with source event counts. Structural lines, +aggregate source lines, notes, blanks, and unclassified events are source events +but are not tests. A test-count sum mismatch is permitted only when +`AGGREGATE_CONTRADICTION` explicitly makes the receipt insufficient. + +Hash-only warning or unclassified locators remain model-visible references, but +the underlying source events count as suppressed because their text is not +visible. This matches packet-v1 accounting rather than inventing a second event. + +## Measurement and payload invariants + +- `reduced_bytes` equals canonical packet bytes including the final newline. +- Declared ceiling and requested ceiling are identical. +- A non-null honored ceiling cannot be smaller than `reduced_bytes`. +- Payload-affected state has exactly one applicable payload reason. +- Reference mode explicitly declares omitted noncritical text. +- Compact mode explicitly declares critical evidence exceeded, retains failure + identities, retains no source-line text, and is always insufficient. +- Negative, non-finite, fractional, unsafe-integer, and contradictory numeric + records are rejected. + +The validator does not require reduced bytes to be smaller than original bytes. +Small valid receipts can expand because provenance has fixed cost. + +## Sufficiency and escalation + +`SUFFICIENT` is valid only when `reason_codes` is empty, +`escalation_required` is false, and `reduction_complete` is true. + +Any uncertainty or omission reason requires all of: + +- disposition `NEEDS_RAW_EVIDENCE`; +- `evidence_sufficient: false`; +- `raw_evidence_required: true`; +- `escalation_required: true`; and +- `reduction_complete: false`. + +This covers ambiguous/unclassified evidence, malformed UTF-8, aggregate +contradiction, interrupted or missing process state, missing provenance, +unexplained nonzero exit, missing raw reference, noncritical text omission, and +critical evidence exceeding the ceiling. + +Suppressed evidence with a caller reference is retrievable only as a caller +claim. Suppressed evidence without a reference is preservation-unconfirmed. +Retained evidence is model-visible. Critical omitted evidence requires raw +escalation. These states are not interchangeable. + +## Conformance and interoperability + +Run self-contained tests and vectors: + +```bash +npm test +npm run conformance +``` + +Run the separate exact-revision producer proof: + +```bash +node \ + tools/verify-context-firewall-interop.js \ + ../context-firewall +``` + +Normal CI uses only checked-in protocol vectors. The interoperability command +loads the read-only sibling checkout to generate current packets for normal +success, failure, suppression-heavy, and raw-escalation cases, then rejects a +tampered copy. It is deterministic and launches no model or provider workload. diff --git a/examples/README.md b/examples/README.md index ab3eb89..5700ff3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,3 +1,6 @@ # Examples -Examples must use sanitized synthetic identities and demonstrate generic integration rather than Taslos Tasks internals. +Examples and checked-in vectors use sanitized synthetic identities and generic +integration only. See +[`fixtures/context-firewall-packet-v1/vectors.json`](../fixtures/context-firewall-packet-v1/vectors.json) +for complete public-safe packet and source-input pairs. diff --git a/fixtures/README.md b/fixtures/README.md new file mode 100644 index 0000000..9043f19 --- /dev/null +++ b/fixtures/README.md @@ -0,0 +1,25 @@ +# Conformance fixtures + +`context-firewall-packet-v1/vectors.json` is a public-safe, protocol-owned corpus +generated from the exact Context Firewall revision recorded in the file. + +The 9 valid vectors cover successful reduction, failed and multi-failure tests, +large success suppression, a caller raw reference, ordinary and ambiguous raw +escalation, ceiling-triggered critical omission, and an exact payload boundary. + +The 15 invalid vectors cover missing fields, unsupported versions, malformed and +wrong hashes, tampered source bytes, impossible bytes and event counts, +contradictory suppression, invalid escalation, negative and overflow-like +measurements, malformed raw locators, false sufficiency, and wrong canonical +output measurement. + +Normal CI never imports Context Firewall. Maintainers can refresh vectors only +from the pinned read-only producer revision: + +```bash +node \ + tools/generate-context-firewall-vectors.js \ + --context-firewall ../context-firewall +``` + +Review generator and vector diffs before accepting a producer revision change. diff --git a/fixtures/context-firewall-packet-v1/vectors.json b/fixtures/context-firewall-packet-v1/vectors.json new file mode 100644 index 0000000..9849d14 --- /dev/null +++ b/fixtures/context-firewall-packet-v1/vectors.json @@ -0,0 +1,1552 @@ +{ + "context_firewall_revision": "dd34bd9f681314761f1ca87f339648bf611811f3", + "invalid_vectors": [ + { + "id": "missing-required-field", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "remove", + "path": "/receipt/input_hash" + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "MALFORMED_SHA256" + ] + } + }, + { + "id": "unsupported-schema-version", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/protocol_version", + "value": "opsle.context-firewall.evidence-packet/v2" + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "UNSUPPORTED_PACKET_VERSION" + ] + } + }, + { + "id": "malformed-hash", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/input_hash", + "value": "sha256:not-a-digest" + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "MALFORMED_SHA256" + ] + } + }, + { + "id": "wrong-source-hash", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/input_hash", + "value": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "CRYPTOGRAPHIC_MISMATCH", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "INPUT_HASH_MISMATCH" + ] + } + }, + { + "id": "wrong-reduced-packet-hash", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/semantic_payload_hash", + "value": "sha256:1111111111111111111111111111111111111111111111111111111111111111" + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "CRYPTOGRAPHIC_MISMATCH", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "SEMANTIC_PAYLOAD_HASH_MISMATCH" + ] + } + }, + { + "id": "tampered-source-bytes", + "base": "successful-reduced-run", + "source_mutations": [ + { + "op": "replace", + "path": "/streams/0/data", + "value": "TAP version 13\nok 1 - tampered\n1..1\n# tests 1\n# pass 1\n# fail 0\n# skipped 0\n" + } + ], + "expected": { + "classification": "CRYPTOGRAPHIC_MISMATCH", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "INPUT_HASH_MISMATCH" + ] + } + }, + { + "id": "impossible-byte-counts", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/measurements/original_bytes", + "value": 105 + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "ORIGINAL_BYTE_COUNT_MISMATCH" + ] + } + }, + { + "id": "impossible-evidence-counts", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/measurements/original_event_count", + "value": 1 + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "EVIDENCE_ACCOUNTING_MISMATCH" + ] + } + }, + { + "id": "contradictory-suppression-accounting", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/suppressed/categories/successful_test", + "value": 3 + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "SUPPRESSION_CATEGORY_TOTAL_MISMATCH" + ] + } + }, + { + "id": "invalid-escalation-state", + "base": "valid-needs-raw-evidence", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/raw_evidence/escalation_required", + "value": false + } + ], + "refresh_reduced_bytes": true, + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "ESCALATION_REQUIRED_MISMATCH" + ] + } + }, + { + "id": "negative-measurement", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/measurements/suppressed_evidence_count", + "value": -1 + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "INVALID_MEASUREMENT" + ] + } + }, + { + "id": "overflow-like-measurement", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/measurements/original_bytes", + "value": 9007199254740992 + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "INVALID_MEASUREMENT" + ] + } + }, + { + "id": "malformed-raw-evidence-locator", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/raw_evidence/reference", + "value": "" + } + ], + "expected": { + "classification": "STRUCTURALLY_INVALID", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "INVALID_IDENTITY" + ] + } + }, + { + "id": "false-sufficiency-claim", + "base": "valid-needs-raw-evidence", + "packet_mutations": [ + { + "op": "replace", + "path": "/decision_evidence/disposition", + "value": "SUFFICIENT" + } + ], + "refresh_semantic_hash": true, + "refresh_reduced_bytes": true, + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "DISPOSITION_MISMATCH" + ] + } + }, + { + "id": "wrong-reduced-byte-measurement", + "base": "successful-reduced-run", + "packet_mutations": [ + { + "op": "replace", + "path": "/receipt/measurements/reduced_bytes", + "value": 1 + } + ], + "expected": { + "classification": "INTERNALLY_INCONSISTENT", + "sufficiency": "INVALID", + "valid": false, + "violation_codes": [ + "REDUCED_BYTE_COUNT_MISMATCH" + ] + } + } + ], + "protocol_version": "opsle.decision-evidence.context-firewall-conformance/v1", + "valid_vectors": [ + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "successful-reduced-run", + "packet": { + "decision_evidence": { + "counts": { + "passed": 2, + "failed": 0, + "skipped": 0, + "total": 2 + }, + "disposition": "SUFFICIENT", + "failures": [], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 0, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "passed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:a241d570cd2f4cbfc1e3769c3606b21317e78cae1ff298abf69223e09915480d", + "measurements": { + "original_bytes": 104, + "original_event_count": 9, + "reduced_bytes": 1824, + "retained_evidence_count": 0, + "suppressed_evidence_count": 9 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 0 + }, + "semantic_payload_hash": "sha256:c5f859435187b89c8377b6426bfdf236a50e139fa632274ef08ae0445a64b006", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 104, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2, + "successful_test": 2 + }, + "event_count": 9 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "normal/small-all-pass", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 0, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nok 1 - alpha\nok 2 - beta\n1..2\n# tests 2\n# pass 2\n# fail 0\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "failed-test-packet", + "packet": { + "decision_evidence": { + "counts": { + "passed": 0, + "failed": 1, + "skipped": 0, + "total": 1 + }, + "disposition": "SUFFICIENT", + "failures": [ + { + "identity": "adds values", + "header": { + "category": "failed_test", + "line": 2, + "sha256": "sha256:046179bd0e736713246c06c25fa603d820a5bde944eb99381daf58f871c8eaf7", + "stream": "stdout", + "text": "not ok 1 - adds values" + }, + "details": [ + { + "category": "failure_message", + "line": 3, + "sha256": "sha256:4cddaa4098315b5304f16a9254e6977a2ac61a0dff32f6c771134c0ccbf20bfa", + "stream": "stdout", + "text": " message: expected two" + }, + { + "category": "assertion", + "line": 4, + "sha256": "sha256:ad97252a03a228eb41f486aa996ed21f69d9c63c236dfbc48b41b736e5480c26", + "stream": "stdout", + "text": " expected: 2" + }, + { + "category": "assertion", + "line": 5, + "sha256": "sha256:79e2b90d2f8c4f4a419caeffdc11d9c75ef54a6c7e3ef172f088c55c88bedd45", + "stream": "stdout", + "text": " actual: 3" + } + ] + } + ], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 1, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "failed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:b39c39b5cc00dd7177e6628b3a247967f2d9ad10b24a67f0512bed5321a83e12", + "measurements": { + "original_bytes": 152, + "original_event_count": 11, + "reduced_bytes": 2553, + "retained_evidence_count": 4, + "suppressed_evidence_count": 7 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "assertion", + "failed_test", + "failure_message", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 4 + }, + "semantic_payload_hash": "sha256:a5aef42a220e82a4d629e81cadb3d2c14cf00cb383a7ba73abf96fa70261538f", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 152, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2 + }, + "event_count": 7 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "failure/one", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 1, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nnot ok 1 - adds values\n message: expected two\n expected: 2\n actual: 3\n1..1\n# tests 1\n# pass 0\n# fail 1\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "multiple-failures", + "packet": { + "decision_evidence": { + "counts": { + "passed": 0, + "failed": 3, + "skipped": 0, + "total": 3 + }, + "disposition": "SUFFICIENT", + "failures": [ + { + "identity": "first", + "header": { + "category": "failed_test", + "line": 2, + "sha256": "sha256:0ca8fead994ad5f1e121620c466a76223932d55ab606d0d395816d9a6a27771b", + "stream": "stdout", + "text": "not ok 1 - first" + }, + "details": [ + { + "category": "failure_message", + "line": 3, + "sha256": "sha256:4480363b481c43cf8e8962bac58be2166bf191fbe0ea98253f7417629c49f8b6", + "stream": "stdout", + "text": " message: first broke" + } + ] + }, + { + "identity": "second", + "header": { + "category": "failed_test", + "line": 4, + "sha256": "sha256:51a606948184b3a8dd565e37efc3a31972518cfd28a887164780dc1d397dd1f2", + "stream": "stdout", + "text": "not ok 2 - second" + }, + "details": [ + { + "category": "failure_message", + "line": 5, + "sha256": "sha256:ad3975f0874b3a6cdd116a56575bbb643772984c4c8974cf4c35b45a37ea0a6f", + "stream": "stdout", + "text": " message: second broke" + } + ] + }, + { + "identity": "third", + "header": { + "category": "failed_test", + "line": 6, + "sha256": "sha256:86f968b72a5f324a8f2c3a28cbce75d5ef7c918523f053d96c68ebf0eb586c9f", + "stream": "stdout", + "text": "not ok 3 - third" + }, + "details": [ + { + "category": "failure_message", + "line": 7, + "sha256": "sha256:a6a911cae804f1913d3223879f44b2902b7d881fecdc7fa6ceee127173b0ecb9", + "stream": "stdout", + "text": " message: third broke" + } + ] + } + ], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 1, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "failed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:104a99176603c3bb2d387260331e5cb1a199c77f0bc722baecd575dc806e50bc", + "measurements": { + "original_bytes": 201, + "original_event_count": 13, + "reduced_bytes": 2973, + "retained_evidence_count": 6, + "suppressed_evidence_count": 7 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "failed_test", + "failure_message", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 6 + }, + "semantic_payload_hash": "sha256:c09851d8d77f5525b9515d5194e690166a6d1d68a368bdc53777e2118f36974c", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 201, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2 + }, + "event_count": 7 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "failure/several", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 1, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nnot ok 1 - first\n message: first broke\nnot ok 2 - second\n message: second broke\nnot ok 3 - third\n message: third broke\n1..3\n# tests 3\n# pass 0\n# fail 3\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "large-successful-suppression", + "packet": { + "decision_evidence": { + "counts": { + "passed": 1500, + "failed": 0, + "skipped": 0, + "total": 1500 + }, + "disposition": "SUFFICIENT", + "failures": [], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 0, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "passed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:82ea05bab3fc9551830551372723ab484c5893f93b9ee88bbfaf9a1e9cf42abc", + "measurements": { + "original_bytes": 28981, + "original_event_count": 1507, + "reduced_bytes": 1846, + "retained_evidence_count": 0, + "suppressed_evidence_count": 1507 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 0 + }, + "semantic_payload_hash": "sha256:d99849b21430365b8b3aefd974d26d625092874054c8c09d1eea69e84e9a4e49", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 28981, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2, + "successful_test": 1500 + }, + "event_count": 1507 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "normal/large-all-pass", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 0, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nok 1 - case-0001\nok 2 - case-0002\nok 3 - case-0003\nok 4 - case-0004\nok 5 - case-0005\nok 6 - case-0006\nok 7 - case-0007\nok 8 - case-0008\nok 9 - case-0009\nok 10 - case-0010\nok 11 - case-0011\nok 12 - case-0012\nok 13 - case-0013\nok 14 - case-0014\nok 15 - case-0015\nok 16 - case-0016\nok 17 - case-0017\nok 18 - case-0018\nok 19 - case-0019\nok 20 - case-0020\nok 21 - case-0021\nok 22 - case-0022\nok 23 - case-0023\nok 24 - case-0024\nok 25 - case-0025\nok 26 - case-0026\nok 27 - case-0027\nok 28 - case-0028\nok 29 - case-0029\nok 30 - case-0030\nok 31 - case-0031\nok 32 - case-0032\nok 33 - case-0033\nok 34 - case-0034\nok 35 - case-0035\nok 36 - case-0036\nok 37 - case-0037\nok 38 - case-0038\nok 39 - case-0039\nok 40 - case-0040\nok 41 - case-0041\nok 42 - case-0042\nok 43 - case-0043\nok 44 - case-0044\nok 45 - case-0045\nok 46 - case-0046\nok 47 - case-0047\nok 48 - case-0048\nok 49 - case-0049\nok 50 - case-0050\nok 51 - case-0051\nok 52 - case-0052\nok 53 - case-0053\nok 54 - case-0054\nok 55 - case-0055\nok 56 - case-0056\nok 57 - case-0057\nok 58 - case-0058\nok 59 - case-0059\nok 60 - case-0060\nok 61 - case-0061\nok 62 - case-0062\nok 63 - case-0063\nok 64 - case-0064\nok 65 - case-0065\nok 66 - case-0066\nok 67 - case-0067\nok 68 - case-0068\nok 69 - case-0069\nok 70 - case-0070\nok 71 - case-0071\nok 72 - case-0072\nok 73 - case-0073\nok 74 - case-0074\nok 75 - case-0075\nok 76 - case-0076\nok 77 - case-0077\nok 78 - case-0078\nok 79 - case-0079\nok 80 - case-0080\nok 81 - case-0081\nok 82 - case-0082\nok 83 - case-0083\nok 84 - case-0084\nok 85 - case-0085\nok 86 - case-0086\nok 87 - case-0087\nok 88 - case-0088\nok 89 - case-0089\nok 90 - case-0090\nok 91 - case-0091\nok 92 - case-0092\nok 93 - case-0093\nok 94 - case-0094\nok 95 - case-0095\nok 96 - case-0096\nok 97 - case-0097\nok 98 - case-0098\nok 99 - case-0099\nok 100 - case-0100\nok 101 - case-0101\nok 102 - case-0102\nok 103 - case-0103\nok 104 - case-0104\nok 105 - case-0105\nok 106 - case-0106\nok 107 - case-0107\nok 108 - case-0108\nok 109 - case-0109\nok 110 - case-0110\nok 111 - case-0111\nok 112 - case-0112\nok 113 - case-0113\nok 114 - case-0114\nok 115 - case-0115\nok 116 - case-0116\nok 117 - case-0117\nok 118 - case-0118\nok 119 - case-0119\nok 120 - case-0120\nok 121 - case-0121\nok 122 - case-0122\nok 123 - case-0123\nok 124 - case-0124\nok 125 - case-0125\nok 126 - case-0126\nok 127 - case-0127\nok 128 - case-0128\nok 129 - case-0129\nok 130 - case-0130\nok 131 - case-0131\nok 132 - case-0132\nok 133 - case-0133\nok 134 - case-0134\nok 135 - case-0135\nok 136 - case-0136\nok 137 - case-0137\nok 138 - case-0138\nok 139 - case-0139\nok 140 - case-0140\nok 141 - case-0141\nok 142 - case-0142\nok 143 - case-0143\nok 144 - case-0144\nok 145 - case-0145\nok 146 - case-0146\nok 147 - case-0147\nok 148 - case-0148\nok 149 - case-0149\nok 150 - case-0150\nok 151 - case-0151\nok 152 - case-0152\nok 153 - case-0153\nok 154 - case-0154\nok 155 - case-0155\nok 156 - case-0156\nok 157 - case-0157\nok 158 - case-0158\nok 159 - case-0159\nok 160 - case-0160\nok 161 - case-0161\nok 162 - case-0162\nok 163 - case-0163\nok 164 - case-0164\nok 165 - case-0165\nok 166 - case-0166\nok 167 - case-0167\nok 168 - case-0168\nok 169 - case-0169\nok 170 - case-0170\nok 171 - case-0171\nok 172 - case-0172\nok 173 - case-0173\nok 174 - case-0174\nok 175 - case-0175\nok 176 - case-0176\nok 177 - case-0177\nok 178 - case-0178\nok 179 - case-0179\nok 180 - case-0180\nok 181 - case-0181\nok 182 - case-0182\nok 183 - case-0183\nok 184 - case-0184\nok 185 - case-0185\nok 186 - case-0186\nok 187 - case-0187\nok 188 - case-0188\nok 189 - case-0189\nok 190 - case-0190\nok 191 - case-0191\nok 192 - case-0192\nok 193 - case-0193\nok 194 - case-0194\nok 195 - case-0195\nok 196 - case-0196\nok 197 - case-0197\nok 198 - case-0198\nok 199 - case-0199\nok 200 - case-0200\nok 201 - case-0201\nok 202 - case-0202\nok 203 - case-0203\nok 204 - case-0204\nok 205 - case-0205\nok 206 - case-0206\nok 207 - case-0207\nok 208 - case-0208\nok 209 - case-0209\nok 210 - case-0210\nok 211 - case-0211\nok 212 - case-0212\nok 213 - case-0213\nok 214 - case-0214\nok 215 - case-0215\nok 216 - case-0216\nok 217 - case-0217\nok 218 - case-0218\nok 219 - case-0219\nok 220 - case-0220\nok 221 - case-0221\nok 222 - case-0222\nok 223 - case-0223\nok 224 - case-0224\nok 225 - case-0225\nok 226 - case-0226\nok 227 - case-0227\nok 228 - case-0228\nok 229 - case-0229\nok 230 - case-0230\nok 231 - case-0231\nok 232 - case-0232\nok 233 - case-0233\nok 234 - case-0234\nok 235 - case-0235\nok 236 - case-0236\nok 237 - case-0237\nok 238 - case-0238\nok 239 - case-0239\nok 240 - case-0240\nok 241 - case-0241\nok 242 - case-0242\nok 243 - case-0243\nok 244 - case-0244\nok 245 - case-0245\nok 246 - case-0246\nok 247 - case-0247\nok 248 - case-0248\nok 249 - case-0249\nok 250 - case-0250\nok 251 - case-0251\nok 252 - case-0252\nok 253 - case-0253\nok 254 - case-0254\nok 255 - case-0255\nok 256 - case-0256\nok 257 - case-0257\nok 258 - case-0258\nok 259 - case-0259\nok 260 - case-0260\nok 261 - case-0261\nok 262 - case-0262\nok 263 - case-0263\nok 264 - case-0264\nok 265 - case-0265\nok 266 - case-0266\nok 267 - case-0267\nok 268 - case-0268\nok 269 - case-0269\nok 270 - case-0270\nok 271 - case-0271\nok 272 - case-0272\nok 273 - case-0273\nok 274 - case-0274\nok 275 - case-0275\nok 276 - case-0276\nok 277 - case-0277\nok 278 - case-0278\nok 279 - case-0279\nok 280 - case-0280\nok 281 - case-0281\nok 282 - case-0282\nok 283 - case-0283\nok 284 - case-0284\nok 285 - case-0285\nok 286 - case-0286\nok 287 - case-0287\nok 288 - case-0288\nok 289 - case-0289\nok 290 - case-0290\nok 291 - case-0291\nok 292 - case-0292\nok 293 - case-0293\nok 294 - case-0294\nok 295 - case-0295\nok 296 - case-0296\nok 297 - case-0297\nok 298 - case-0298\nok 299 - case-0299\nok 300 - case-0300\nok 301 - case-0301\nok 302 - case-0302\nok 303 - case-0303\nok 304 - case-0304\nok 305 - case-0305\nok 306 - case-0306\nok 307 - case-0307\nok 308 - case-0308\nok 309 - case-0309\nok 310 - case-0310\nok 311 - case-0311\nok 312 - case-0312\nok 313 - case-0313\nok 314 - case-0314\nok 315 - case-0315\nok 316 - case-0316\nok 317 - case-0317\nok 318 - case-0318\nok 319 - case-0319\nok 320 - case-0320\nok 321 - case-0321\nok 322 - case-0322\nok 323 - case-0323\nok 324 - case-0324\nok 325 - case-0325\nok 326 - case-0326\nok 327 - case-0327\nok 328 - case-0328\nok 329 - case-0329\nok 330 - case-0330\nok 331 - case-0331\nok 332 - case-0332\nok 333 - case-0333\nok 334 - case-0334\nok 335 - case-0335\nok 336 - case-0336\nok 337 - case-0337\nok 338 - case-0338\nok 339 - case-0339\nok 340 - case-0340\nok 341 - case-0341\nok 342 - case-0342\nok 343 - case-0343\nok 344 - case-0344\nok 345 - case-0345\nok 346 - case-0346\nok 347 - case-0347\nok 348 - case-0348\nok 349 - case-0349\nok 350 - case-0350\nok 351 - case-0351\nok 352 - case-0352\nok 353 - case-0353\nok 354 - case-0354\nok 355 - case-0355\nok 356 - case-0356\nok 357 - case-0357\nok 358 - case-0358\nok 359 - case-0359\nok 360 - case-0360\nok 361 - case-0361\nok 362 - case-0362\nok 363 - case-0363\nok 364 - case-0364\nok 365 - case-0365\nok 366 - case-0366\nok 367 - case-0367\nok 368 - case-0368\nok 369 - case-0369\nok 370 - case-0370\nok 371 - case-0371\nok 372 - case-0372\nok 373 - case-0373\nok 374 - case-0374\nok 375 - case-0375\nok 376 - case-0376\nok 377 - case-0377\nok 378 - case-0378\nok 379 - case-0379\nok 380 - case-0380\nok 381 - case-0381\nok 382 - case-0382\nok 383 - case-0383\nok 384 - case-0384\nok 385 - case-0385\nok 386 - case-0386\nok 387 - case-0387\nok 388 - case-0388\nok 389 - case-0389\nok 390 - case-0390\nok 391 - case-0391\nok 392 - case-0392\nok 393 - case-0393\nok 394 - case-0394\nok 395 - case-0395\nok 396 - case-0396\nok 397 - case-0397\nok 398 - case-0398\nok 399 - case-0399\nok 400 - case-0400\nok 401 - case-0401\nok 402 - case-0402\nok 403 - case-0403\nok 404 - case-0404\nok 405 - case-0405\nok 406 - case-0406\nok 407 - case-0407\nok 408 - case-0408\nok 409 - case-0409\nok 410 - case-0410\nok 411 - case-0411\nok 412 - case-0412\nok 413 - case-0413\nok 414 - case-0414\nok 415 - case-0415\nok 416 - case-0416\nok 417 - case-0417\nok 418 - case-0418\nok 419 - case-0419\nok 420 - case-0420\nok 421 - case-0421\nok 422 - case-0422\nok 423 - case-0423\nok 424 - case-0424\nok 425 - case-0425\nok 426 - case-0426\nok 427 - case-0427\nok 428 - case-0428\nok 429 - case-0429\nok 430 - case-0430\nok 431 - case-0431\nok 432 - case-0432\nok 433 - case-0433\nok 434 - case-0434\nok 435 - case-0435\nok 436 - case-0436\nok 437 - case-0437\nok 438 - case-0438\nok 439 - case-0439\nok 440 - case-0440\nok 441 - case-0441\nok 442 - case-0442\nok 443 - case-0443\nok 444 - case-0444\nok 445 - case-0445\nok 446 - case-0446\nok 447 - case-0447\nok 448 - case-0448\nok 449 - case-0449\nok 450 - case-0450\nok 451 - case-0451\nok 452 - case-0452\nok 453 - case-0453\nok 454 - case-0454\nok 455 - case-0455\nok 456 - case-0456\nok 457 - case-0457\nok 458 - case-0458\nok 459 - case-0459\nok 460 - case-0460\nok 461 - case-0461\nok 462 - case-0462\nok 463 - case-0463\nok 464 - case-0464\nok 465 - case-0465\nok 466 - case-0466\nok 467 - case-0467\nok 468 - case-0468\nok 469 - case-0469\nok 470 - case-0470\nok 471 - case-0471\nok 472 - case-0472\nok 473 - case-0473\nok 474 - case-0474\nok 475 - case-0475\nok 476 - case-0476\nok 477 - case-0477\nok 478 - case-0478\nok 479 - case-0479\nok 480 - case-0480\nok 481 - case-0481\nok 482 - case-0482\nok 483 - case-0483\nok 484 - case-0484\nok 485 - case-0485\nok 486 - case-0486\nok 487 - case-0487\nok 488 - case-0488\nok 489 - case-0489\nok 490 - case-0490\nok 491 - case-0491\nok 492 - case-0492\nok 493 - case-0493\nok 494 - case-0494\nok 495 - case-0495\nok 496 - case-0496\nok 497 - case-0497\nok 498 - case-0498\nok 499 - case-0499\nok 500 - case-0500\nok 501 - case-0501\nok 502 - case-0502\nok 503 - case-0503\nok 504 - case-0504\nok 505 - case-0505\nok 506 - case-0506\nok 507 - case-0507\nok 508 - case-0508\nok 509 - case-0509\nok 510 - case-0510\nok 511 - case-0511\nok 512 - case-0512\nok 513 - case-0513\nok 514 - case-0514\nok 515 - case-0515\nok 516 - case-0516\nok 517 - case-0517\nok 518 - case-0518\nok 519 - case-0519\nok 520 - case-0520\nok 521 - case-0521\nok 522 - case-0522\nok 523 - case-0523\nok 524 - case-0524\nok 525 - case-0525\nok 526 - case-0526\nok 527 - case-0527\nok 528 - case-0528\nok 529 - case-0529\nok 530 - case-0530\nok 531 - case-0531\nok 532 - case-0532\nok 533 - case-0533\nok 534 - case-0534\nok 535 - case-0535\nok 536 - case-0536\nok 537 - case-0537\nok 538 - case-0538\nok 539 - case-0539\nok 540 - case-0540\nok 541 - case-0541\nok 542 - case-0542\nok 543 - case-0543\nok 544 - case-0544\nok 545 - case-0545\nok 546 - case-0546\nok 547 - case-0547\nok 548 - case-0548\nok 549 - case-0549\nok 550 - case-0550\nok 551 - case-0551\nok 552 - case-0552\nok 553 - case-0553\nok 554 - case-0554\nok 555 - case-0555\nok 556 - case-0556\nok 557 - case-0557\nok 558 - case-0558\nok 559 - case-0559\nok 560 - case-0560\nok 561 - case-0561\nok 562 - case-0562\nok 563 - case-0563\nok 564 - case-0564\nok 565 - case-0565\nok 566 - case-0566\nok 567 - case-0567\nok 568 - case-0568\nok 569 - case-0569\nok 570 - case-0570\nok 571 - case-0571\nok 572 - case-0572\nok 573 - case-0573\nok 574 - case-0574\nok 575 - case-0575\nok 576 - case-0576\nok 577 - case-0577\nok 578 - case-0578\nok 579 - case-0579\nok 580 - case-0580\nok 581 - case-0581\nok 582 - case-0582\nok 583 - case-0583\nok 584 - case-0584\nok 585 - case-0585\nok 586 - case-0586\nok 587 - case-0587\nok 588 - case-0588\nok 589 - case-0589\nok 590 - case-0590\nok 591 - case-0591\nok 592 - case-0592\nok 593 - case-0593\nok 594 - case-0594\nok 595 - case-0595\nok 596 - case-0596\nok 597 - case-0597\nok 598 - case-0598\nok 599 - case-0599\nok 600 - case-0600\nok 601 - case-0601\nok 602 - case-0602\nok 603 - case-0603\nok 604 - case-0604\nok 605 - case-0605\nok 606 - case-0606\nok 607 - case-0607\nok 608 - case-0608\nok 609 - case-0609\nok 610 - case-0610\nok 611 - case-0611\nok 612 - case-0612\nok 613 - case-0613\nok 614 - case-0614\nok 615 - case-0615\nok 616 - case-0616\nok 617 - case-0617\nok 618 - case-0618\nok 619 - case-0619\nok 620 - case-0620\nok 621 - case-0621\nok 622 - case-0622\nok 623 - case-0623\nok 624 - case-0624\nok 625 - case-0625\nok 626 - case-0626\nok 627 - case-0627\nok 628 - case-0628\nok 629 - case-0629\nok 630 - case-0630\nok 631 - case-0631\nok 632 - case-0632\nok 633 - case-0633\nok 634 - case-0634\nok 635 - case-0635\nok 636 - case-0636\nok 637 - case-0637\nok 638 - case-0638\nok 639 - case-0639\nok 640 - case-0640\nok 641 - case-0641\nok 642 - case-0642\nok 643 - case-0643\nok 644 - case-0644\nok 645 - case-0645\nok 646 - case-0646\nok 647 - case-0647\nok 648 - case-0648\nok 649 - case-0649\nok 650 - case-0650\nok 651 - case-0651\nok 652 - case-0652\nok 653 - case-0653\nok 654 - case-0654\nok 655 - case-0655\nok 656 - case-0656\nok 657 - case-0657\nok 658 - case-0658\nok 659 - case-0659\nok 660 - case-0660\nok 661 - case-0661\nok 662 - case-0662\nok 663 - case-0663\nok 664 - case-0664\nok 665 - case-0665\nok 666 - case-0666\nok 667 - case-0667\nok 668 - case-0668\nok 669 - case-0669\nok 670 - case-0670\nok 671 - case-0671\nok 672 - case-0672\nok 673 - case-0673\nok 674 - case-0674\nok 675 - case-0675\nok 676 - case-0676\nok 677 - case-0677\nok 678 - case-0678\nok 679 - case-0679\nok 680 - case-0680\nok 681 - case-0681\nok 682 - case-0682\nok 683 - case-0683\nok 684 - case-0684\nok 685 - case-0685\nok 686 - case-0686\nok 687 - case-0687\nok 688 - case-0688\nok 689 - case-0689\nok 690 - case-0690\nok 691 - case-0691\nok 692 - case-0692\nok 693 - case-0693\nok 694 - case-0694\nok 695 - case-0695\nok 696 - case-0696\nok 697 - case-0697\nok 698 - case-0698\nok 699 - case-0699\nok 700 - case-0700\nok 701 - case-0701\nok 702 - case-0702\nok 703 - case-0703\nok 704 - case-0704\nok 705 - case-0705\nok 706 - case-0706\nok 707 - case-0707\nok 708 - case-0708\nok 709 - case-0709\nok 710 - case-0710\nok 711 - case-0711\nok 712 - case-0712\nok 713 - case-0713\nok 714 - case-0714\nok 715 - case-0715\nok 716 - case-0716\nok 717 - case-0717\nok 718 - case-0718\nok 719 - case-0719\nok 720 - case-0720\nok 721 - case-0721\nok 722 - case-0722\nok 723 - case-0723\nok 724 - case-0724\nok 725 - case-0725\nok 726 - case-0726\nok 727 - case-0727\nok 728 - case-0728\nok 729 - case-0729\nok 730 - case-0730\nok 731 - case-0731\nok 732 - case-0732\nok 733 - case-0733\nok 734 - case-0734\nok 735 - case-0735\nok 736 - case-0736\nok 737 - case-0737\nok 738 - case-0738\nok 739 - case-0739\nok 740 - case-0740\nok 741 - case-0741\nok 742 - case-0742\nok 743 - case-0743\nok 744 - case-0744\nok 745 - case-0745\nok 746 - case-0746\nok 747 - case-0747\nok 748 - case-0748\nok 749 - case-0749\nok 750 - case-0750\nok 751 - case-0751\nok 752 - case-0752\nok 753 - case-0753\nok 754 - case-0754\nok 755 - case-0755\nok 756 - case-0756\nok 757 - case-0757\nok 758 - case-0758\nok 759 - case-0759\nok 760 - case-0760\nok 761 - case-0761\nok 762 - case-0762\nok 763 - case-0763\nok 764 - case-0764\nok 765 - case-0765\nok 766 - case-0766\nok 767 - case-0767\nok 768 - case-0768\nok 769 - case-0769\nok 770 - case-0770\nok 771 - case-0771\nok 772 - case-0772\nok 773 - case-0773\nok 774 - case-0774\nok 775 - case-0775\nok 776 - case-0776\nok 777 - case-0777\nok 778 - case-0778\nok 779 - case-0779\nok 780 - case-0780\nok 781 - case-0781\nok 782 - case-0782\nok 783 - case-0783\nok 784 - case-0784\nok 785 - case-0785\nok 786 - case-0786\nok 787 - case-0787\nok 788 - case-0788\nok 789 - case-0789\nok 790 - case-0790\nok 791 - case-0791\nok 792 - case-0792\nok 793 - case-0793\nok 794 - case-0794\nok 795 - case-0795\nok 796 - case-0796\nok 797 - case-0797\nok 798 - case-0798\nok 799 - case-0799\nok 800 - case-0800\nok 801 - case-0801\nok 802 - case-0802\nok 803 - case-0803\nok 804 - case-0804\nok 805 - case-0805\nok 806 - case-0806\nok 807 - case-0807\nok 808 - case-0808\nok 809 - case-0809\nok 810 - case-0810\nok 811 - case-0811\nok 812 - case-0812\nok 813 - case-0813\nok 814 - case-0814\nok 815 - case-0815\nok 816 - case-0816\nok 817 - case-0817\nok 818 - case-0818\nok 819 - case-0819\nok 820 - case-0820\nok 821 - case-0821\nok 822 - case-0822\nok 823 - case-0823\nok 824 - case-0824\nok 825 - case-0825\nok 826 - case-0826\nok 827 - case-0827\nok 828 - case-0828\nok 829 - case-0829\nok 830 - case-0830\nok 831 - case-0831\nok 832 - case-0832\nok 833 - case-0833\nok 834 - case-0834\nok 835 - case-0835\nok 836 - case-0836\nok 837 - case-0837\nok 838 - case-0838\nok 839 - case-0839\nok 840 - case-0840\nok 841 - case-0841\nok 842 - case-0842\nok 843 - case-0843\nok 844 - case-0844\nok 845 - case-0845\nok 846 - case-0846\nok 847 - case-0847\nok 848 - case-0848\nok 849 - case-0849\nok 850 - case-0850\nok 851 - case-0851\nok 852 - case-0852\nok 853 - case-0853\nok 854 - case-0854\nok 855 - case-0855\nok 856 - case-0856\nok 857 - case-0857\nok 858 - case-0858\nok 859 - case-0859\nok 860 - case-0860\nok 861 - case-0861\nok 862 - case-0862\nok 863 - case-0863\nok 864 - case-0864\nok 865 - case-0865\nok 866 - case-0866\nok 867 - case-0867\nok 868 - case-0868\nok 869 - case-0869\nok 870 - case-0870\nok 871 - case-0871\nok 872 - case-0872\nok 873 - case-0873\nok 874 - case-0874\nok 875 - case-0875\nok 876 - case-0876\nok 877 - case-0877\nok 878 - case-0878\nok 879 - case-0879\nok 880 - case-0880\nok 881 - case-0881\nok 882 - case-0882\nok 883 - case-0883\nok 884 - case-0884\nok 885 - case-0885\nok 886 - case-0886\nok 887 - case-0887\nok 888 - case-0888\nok 889 - case-0889\nok 890 - case-0890\nok 891 - case-0891\nok 892 - case-0892\nok 893 - case-0893\nok 894 - case-0894\nok 895 - case-0895\nok 896 - case-0896\nok 897 - case-0897\nok 898 - case-0898\nok 899 - case-0899\nok 900 - case-0900\nok 901 - case-0901\nok 902 - case-0902\nok 903 - case-0903\nok 904 - case-0904\nok 905 - case-0905\nok 906 - case-0906\nok 907 - case-0907\nok 908 - case-0908\nok 909 - case-0909\nok 910 - case-0910\nok 911 - case-0911\nok 912 - case-0912\nok 913 - case-0913\nok 914 - case-0914\nok 915 - case-0915\nok 916 - case-0916\nok 917 - case-0917\nok 918 - case-0918\nok 919 - case-0919\nok 920 - case-0920\nok 921 - case-0921\nok 922 - case-0922\nok 923 - case-0923\nok 924 - case-0924\nok 925 - case-0925\nok 926 - case-0926\nok 927 - case-0927\nok 928 - case-0928\nok 929 - case-0929\nok 930 - case-0930\nok 931 - case-0931\nok 932 - case-0932\nok 933 - case-0933\nok 934 - case-0934\nok 935 - case-0935\nok 936 - case-0936\nok 937 - case-0937\nok 938 - case-0938\nok 939 - case-0939\nok 940 - case-0940\nok 941 - case-0941\nok 942 - case-0942\nok 943 - case-0943\nok 944 - case-0944\nok 945 - case-0945\nok 946 - case-0946\nok 947 - case-0947\nok 948 - case-0948\nok 949 - case-0949\nok 950 - case-0950\nok 951 - case-0951\nok 952 - case-0952\nok 953 - case-0953\nok 954 - case-0954\nok 955 - case-0955\nok 956 - case-0956\nok 957 - case-0957\nok 958 - case-0958\nok 959 - case-0959\nok 960 - case-0960\nok 961 - case-0961\nok 962 - case-0962\nok 963 - case-0963\nok 964 - case-0964\nok 965 - case-0965\nok 966 - case-0966\nok 967 - case-0967\nok 968 - case-0968\nok 969 - case-0969\nok 970 - case-0970\nok 971 - case-0971\nok 972 - case-0972\nok 973 - case-0973\nok 974 - case-0974\nok 975 - case-0975\nok 976 - case-0976\nok 977 - case-0977\nok 978 - case-0978\nok 979 - case-0979\nok 980 - case-0980\nok 981 - case-0981\nok 982 - case-0982\nok 983 - case-0983\nok 984 - case-0984\nok 985 - case-0985\nok 986 - case-0986\nok 987 - case-0987\nok 988 - case-0988\nok 989 - case-0989\nok 990 - case-0990\nok 991 - case-0991\nok 992 - case-0992\nok 993 - case-0993\nok 994 - case-0994\nok 995 - case-0995\nok 996 - case-0996\nok 997 - case-0997\nok 998 - case-0998\nok 999 - case-0999\nok 1000 - case-1000\nok 1001 - case-1001\nok 1002 - case-1002\nok 1003 - case-1003\nok 1004 - case-1004\nok 1005 - case-1005\nok 1006 - case-1006\nok 1007 - case-1007\nok 1008 - case-1008\nok 1009 - case-1009\nok 1010 - case-1010\nok 1011 - case-1011\nok 1012 - case-1012\nok 1013 - case-1013\nok 1014 - case-1014\nok 1015 - case-1015\nok 1016 - case-1016\nok 1017 - case-1017\nok 1018 - case-1018\nok 1019 - case-1019\nok 1020 - case-1020\nok 1021 - case-1021\nok 1022 - case-1022\nok 1023 - case-1023\nok 1024 - case-1024\nok 1025 - case-1025\nok 1026 - case-1026\nok 1027 - case-1027\nok 1028 - case-1028\nok 1029 - case-1029\nok 1030 - case-1030\nok 1031 - case-1031\nok 1032 - case-1032\nok 1033 - case-1033\nok 1034 - case-1034\nok 1035 - case-1035\nok 1036 - case-1036\nok 1037 - case-1037\nok 1038 - case-1038\nok 1039 - case-1039\nok 1040 - case-1040\nok 1041 - case-1041\nok 1042 - case-1042\nok 1043 - case-1043\nok 1044 - case-1044\nok 1045 - case-1045\nok 1046 - case-1046\nok 1047 - case-1047\nok 1048 - case-1048\nok 1049 - case-1049\nok 1050 - case-1050\nok 1051 - case-1051\nok 1052 - case-1052\nok 1053 - case-1053\nok 1054 - case-1054\nok 1055 - case-1055\nok 1056 - case-1056\nok 1057 - case-1057\nok 1058 - case-1058\nok 1059 - case-1059\nok 1060 - case-1060\nok 1061 - case-1061\nok 1062 - case-1062\nok 1063 - case-1063\nok 1064 - case-1064\nok 1065 - case-1065\nok 1066 - case-1066\nok 1067 - case-1067\nok 1068 - case-1068\nok 1069 - case-1069\nok 1070 - case-1070\nok 1071 - case-1071\nok 1072 - case-1072\nok 1073 - case-1073\nok 1074 - case-1074\nok 1075 - case-1075\nok 1076 - case-1076\nok 1077 - case-1077\nok 1078 - case-1078\nok 1079 - case-1079\nok 1080 - case-1080\nok 1081 - case-1081\nok 1082 - case-1082\nok 1083 - case-1083\nok 1084 - case-1084\nok 1085 - case-1085\nok 1086 - case-1086\nok 1087 - case-1087\nok 1088 - case-1088\nok 1089 - case-1089\nok 1090 - case-1090\nok 1091 - case-1091\nok 1092 - case-1092\nok 1093 - case-1093\nok 1094 - case-1094\nok 1095 - case-1095\nok 1096 - case-1096\nok 1097 - case-1097\nok 1098 - case-1098\nok 1099 - case-1099\nok 1100 - case-1100\nok 1101 - case-1101\nok 1102 - case-1102\nok 1103 - case-1103\nok 1104 - case-1104\nok 1105 - case-1105\nok 1106 - case-1106\nok 1107 - case-1107\nok 1108 - case-1108\nok 1109 - case-1109\nok 1110 - case-1110\nok 1111 - case-1111\nok 1112 - case-1112\nok 1113 - case-1113\nok 1114 - case-1114\nok 1115 - case-1115\nok 1116 - case-1116\nok 1117 - case-1117\nok 1118 - case-1118\nok 1119 - case-1119\nok 1120 - case-1120\nok 1121 - case-1121\nok 1122 - case-1122\nok 1123 - case-1123\nok 1124 - case-1124\nok 1125 - case-1125\nok 1126 - case-1126\nok 1127 - case-1127\nok 1128 - case-1128\nok 1129 - case-1129\nok 1130 - case-1130\nok 1131 - case-1131\nok 1132 - case-1132\nok 1133 - case-1133\nok 1134 - case-1134\nok 1135 - case-1135\nok 1136 - case-1136\nok 1137 - case-1137\nok 1138 - case-1138\nok 1139 - case-1139\nok 1140 - case-1140\nok 1141 - case-1141\nok 1142 - case-1142\nok 1143 - case-1143\nok 1144 - case-1144\nok 1145 - case-1145\nok 1146 - case-1146\nok 1147 - case-1147\nok 1148 - case-1148\nok 1149 - case-1149\nok 1150 - case-1150\nok 1151 - case-1151\nok 1152 - case-1152\nok 1153 - case-1153\nok 1154 - case-1154\nok 1155 - case-1155\nok 1156 - case-1156\nok 1157 - case-1157\nok 1158 - case-1158\nok 1159 - case-1159\nok 1160 - case-1160\nok 1161 - case-1161\nok 1162 - case-1162\nok 1163 - case-1163\nok 1164 - case-1164\nok 1165 - case-1165\nok 1166 - case-1166\nok 1167 - case-1167\nok 1168 - case-1168\nok 1169 - case-1169\nok 1170 - case-1170\nok 1171 - case-1171\nok 1172 - case-1172\nok 1173 - case-1173\nok 1174 - case-1174\nok 1175 - case-1175\nok 1176 - case-1176\nok 1177 - case-1177\nok 1178 - case-1178\nok 1179 - case-1179\nok 1180 - case-1180\nok 1181 - case-1181\nok 1182 - case-1182\nok 1183 - case-1183\nok 1184 - case-1184\nok 1185 - case-1185\nok 1186 - case-1186\nok 1187 - case-1187\nok 1188 - case-1188\nok 1189 - case-1189\nok 1190 - case-1190\nok 1191 - case-1191\nok 1192 - case-1192\nok 1193 - case-1193\nok 1194 - case-1194\nok 1195 - case-1195\nok 1196 - case-1196\nok 1197 - case-1197\nok 1198 - case-1198\nok 1199 - case-1199\nok 1200 - case-1200\nok 1201 - case-1201\nok 1202 - case-1202\nok 1203 - case-1203\nok 1204 - case-1204\nok 1205 - case-1205\nok 1206 - case-1206\nok 1207 - case-1207\nok 1208 - case-1208\nok 1209 - case-1209\nok 1210 - case-1210\nok 1211 - case-1211\nok 1212 - case-1212\nok 1213 - case-1213\nok 1214 - case-1214\nok 1215 - case-1215\nok 1216 - case-1216\nok 1217 - case-1217\nok 1218 - case-1218\nok 1219 - case-1219\nok 1220 - case-1220\nok 1221 - case-1221\nok 1222 - case-1222\nok 1223 - case-1223\nok 1224 - case-1224\nok 1225 - case-1225\nok 1226 - case-1226\nok 1227 - case-1227\nok 1228 - case-1228\nok 1229 - case-1229\nok 1230 - case-1230\nok 1231 - case-1231\nok 1232 - case-1232\nok 1233 - case-1233\nok 1234 - case-1234\nok 1235 - case-1235\nok 1236 - case-1236\nok 1237 - case-1237\nok 1238 - case-1238\nok 1239 - case-1239\nok 1240 - case-1240\nok 1241 - case-1241\nok 1242 - case-1242\nok 1243 - case-1243\nok 1244 - case-1244\nok 1245 - case-1245\nok 1246 - case-1246\nok 1247 - case-1247\nok 1248 - case-1248\nok 1249 - case-1249\nok 1250 - case-1250\nok 1251 - case-1251\nok 1252 - case-1252\nok 1253 - case-1253\nok 1254 - case-1254\nok 1255 - case-1255\nok 1256 - case-1256\nok 1257 - case-1257\nok 1258 - case-1258\nok 1259 - case-1259\nok 1260 - case-1260\nok 1261 - case-1261\nok 1262 - case-1262\nok 1263 - case-1263\nok 1264 - case-1264\nok 1265 - case-1265\nok 1266 - case-1266\nok 1267 - case-1267\nok 1268 - case-1268\nok 1269 - case-1269\nok 1270 - case-1270\nok 1271 - case-1271\nok 1272 - case-1272\nok 1273 - case-1273\nok 1274 - case-1274\nok 1275 - case-1275\nok 1276 - case-1276\nok 1277 - case-1277\nok 1278 - case-1278\nok 1279 - case-1279\nok 1280 - case-1280\nok 1281 - case-1281\nok 1282 - case-1282\nok 1283 - case-1283\nok 1284 - case-1284\nok 1285 - case-1285\nok 1286 - case-1286\nok 1287 - case-1287\nok 1288 - case-1288\nok 1289 - case-1289\nok 1290 - case-1290\nok 1291 - case-1291\nok 1292 - case-1292\nok 1293 - case-1293\nok 1294 - case-1294\nok 1295 - case-1295\nok 1296 - case-1296\nok 1297 - case-1297\nok 1298 - case-1298\nok 1299 - case-1299\nok 1300 - case-1300\nok 1301 - case-1301\nok 1302 - case-1302\nok 1303 - case-1303\nok 1304 - case-1304\nok 1305 - case-1305\nok 1306 - case-1306\nok 1307 - case-1307\nok 1308 - case-1308\nok 1309 - case-1309\nok 1310 - case-1310\nok 1311 - case-1311\nok 1312 - case-1312\nok 1313 - case-1313\nok 1314 - case-1314\nok 1315 - case-1315\nok 1316 - case-1316\nok 1317 - case-1317\nok 1318 - case-1318\nok 1319 - case-1319\nok 1320 - case-1320\nok 1321 - case-1321\nok 1322 - case-1322\nok 1323 - case-1323\nok 1324 - case-1324\nok 1325 - case-1325\nok 1326 - case-1326\nok 1327 - case-1327\nok 1328 - case-1328\nok 1329 - case-1329\nok 1330 - case-1330\nok 1331 - case-1331\nok 1332 - case-1332\nok 1333 - case-1333\nok 1334 - case-1334\nok 1335 - case-1335\nok 1336 - case-1336\nok 1337 - case-1337\nok 1338 - case-1338\nok 1339 - case-1339\nok 1340 - case-1340\nok 1341 - case-1341\nok 1342 - case-1342\nok 1343 - case-1343\nok 1344 - case-1344\nok 1345 - case-1345\nok 1346 - case-1346\nok 1347 - case-1347\nok 1348 - case-1348\nok 1349 - case-1349\nok 1350 - case-1350\nok 1351 - case-1351\nok 1352 - case-1352\nok 1353 - case-1353\nok 1354 - case-1354\nok 1355 - case-1355\nok 1356 - case-1356\nok 1357 - case-1357\nok 1358 - case-1358\nok 1359 - case-1359\nok 1360 - case-1360\nok 1361 - case-1361\nok 1362 - case-1362\nok 1363 - case-1363\nok 1364 - case-1364\nok 1365 - case-1365\nok 1366 - case-1366\nok 1367 - case-1367\nok 1368 - case-1368\nok 1369 - case-1369\nok 1370 - case-1370\nok 1371 - case-1371\nok 1372 - case-1372\nok 1373 - case-1373\nok 1374 - case-1374\nok 1375 - case-1375\nok 1376 - case-1376\nok 1377 - case-1377\nok 1378 - case-1378\nok 1379 - case-1379\nok 1380 - case-1380\nok 1381 - case-1381\nok 1382 - case-1382\nok 1383 - case-1383\nok 1384 - case-1384\nok 1385 - case-1385\nok 1386 - case-1386\nok 1387 - case-1387\nok 1388 - case-1388\nok 1389 - case-1389\nok 1390 - case-1390\nok 1391 - case-1391\nok 1392 - case-1392\nok 1393 - case-1393\nok 1394 - case-1394\nok 1395 - case-1395\nok 1396 - case-1396\nok 1397 - case-1397\nok 1398 - case-1398\nok 1399 - case-1399\nok 1400 - case-1400\nok 1401 - case-1401\nok 1402 - case-1402\nok 1403 - case-1403\nok 1404 - case-1404\nok 1405 - case-1405\nok 1406 - case-1406\nok 1407 - case-1407\nok 1408 - case-1408\nok 1409 - case-1409\nok 1410 - case-1410\nok 1411 - case-1411\nok 1412 - case-1412\nok 1413 - case-1413\nok 1414 - case-1414\nok 1415 - case-1415\nok 1416 - case-1416\nok 1417 - case-1417\nok 1418 - case-1418\nok 1419 - case-1419\nok 1420 - case-1420\nok 1421 - case-1421\nok 1422 - case-1422\nok 1423 - case-1423\nok 1424 - case-1424\nok 1425 - case-1425\nok 1426 - case-1426\nok 1427 - case-1427\nok 1428 - case-1428\nok 1429 - case-1429\nok 1430 - case-1430\nok 1431 - case-1431\nok 1432 - case-1432\nok 1433 - case-1433\nok 1434 - case-1434\nok 1435 - case-1435\nok 1436 - case-1436\nok 1437 - case-1437\nok 1438 - case-1438\nok 1439 - case-1439\nok 1440 - case-1440\nok 1441 - case-1441\nok 1442 - case-1442\nok 1443 - case-1443\nok 1444 - case-1444\nok 1445 - case-1445\nok 1446 - case-1446\nok 1447 - case-1447\nok 1448 - case-1448\nok 1449 - case-1449\nok 1450 - case-1450\nok 1451 - case-1451\nok 1452 - case-1452\nok 1453 - case-1453\nok 1454 - case-1454\nok 1455 - case-1455\nok 1456 - case-1456\nok 1457 - case-1457\nok 1458 - case-1458\nok 1459 - case-1459\nok 1460 - case-1460\nok 1461 - case-1461\nok 1462 - case-1462\nok 1463 - case-1463\nok 1464 - case-1464\nok 1465 - case-1465\nok 1466 - case-1466\nok 1467 - case-1467\nok 1468 - case-1468\nok 1469 - case-1469\nok 1470 - case-1470\nok 1471 - case-1471\nok 1472 - case-1472\nok 1473 - case-1473\nok 1474 - case-1474\nok 1475 - case-1475\nok 1476 - case-1476\nok 1477 - case-1477\nok 1478 - case-1478\nok 1479 - case-1479\nok 1480 - case-1480\nok 1481 - case-1481\nok 1482 - case-1482\nok 1483 - case-1483\nok 1484 - case-1484\nok 1485 - case-1485\nok 1486 - case-1486\nok 1487 - case-1487\nok 1488 - case-1488\nok 1489 - case-1489\nok 1490 - case-1490\nok 1491 - case-1491\nok 1492 - case-1492\nok 1493 - case-1493\nok 1494 - case-1494\nok 1495 - case-1495\nok 1496 - case-1496\nok 1497 - case-1497\nok 1498 - case-1498\nok 1499 - case-1499\nok 1500 - case-1500\n1..1500\n# tests 1500\n# pass 1500\n# fail 0\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "valid-raw-evidence-reference", + "packet": { + "decision_evidence": { + "counts": { + "passed": 1, + "failed": 0, + "skipped": 0, + "total": 1 + }, + "disposition": "SUFFICIENT", + "failures": [], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 0, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "passed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:791657819fc5951657ff6cbdaa1fca425944a52aa1613fb2e8cd81603ed58352", + "measurements": { + "original_bytes": 98, + "original_event_count": 8, + "reduced_bytes": 1822, + "retained_evidence_count": 0, + "suppressed_evidence_count": 8 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 0 + }, + "semantic_payload_hash": "sha256:836ed51a0e11a3552de9a3625db6a0558dd8f5538c2f7ba590679790a1e196fb", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 98, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2, + "successful_test": 1 + }, + "event_count": 8 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "provenance/raw-reference-present", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 0, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nok 1 - addressable\n1..1\n# tests 1\n# pass 1\n# fail 0\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "NEEDS_RAW_EVIDENCE", + "valid": true + }, + "id": "valid-needs-raw-evidence", + "packet": { + "decision_evidence": { + "counts": { + "passed": 0, + "failed": 1, + "skipped": 0, + "total": 1 + }, + "disposition": "NEEDS_RAW_EVIDENCE", + "failures": [ + { + "identity": "incomplete", + "header": { + "category": "failed_test", + "line": 2, + "sha256": "sha256:4d0e04d8857550bd5877c17a99149acd0b26514e488fce626b988317aa72ac32", + "stream": "stdout", + "text": "not ok 1 - incomplete" + }, + "details": [ + { + "category": "failure_message", + "line": 3, + "sha256": "sha256:a9d490027eeceb7c3f82d7dce9278f1d449f13f0d5f7a1cf3c21e2117404ee89", + "stream": "stdout", + "text": " message: cut off" + } + ] + } + ], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 1, + "interrupted": true + }, + "reason_codes": [ + "INTERRUPTED_OUTPUT" + ], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "failed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:95e8762ca9ecab63a1e861b6922a93225b3078279824a9d9d0b5ebe5b31adf39", + "measurements": { + "original_bytes": 55, + "original_event_count": 3, + "reduced_bytes": 2203, + "retained_evidence_count": 2, + "suppressed_evidence_count": 1 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": true, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": false, + "retained": { + "categories": [ + "aggregate_counts", + "failed_test", + "failure_message", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 2 + }, + "semantic_payload_hash": "sha256:1966d17b65518bef4547428e166d6fdea14ae5365bdb8a6221356d3c61744368", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 55, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "structure": 1 + }, + "event_count": 1 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "process/interrupted-truncated", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 1, + "duration_ms": 12.5, + "interrupted": true + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nnot ok 1 - incomplete\n message: cut off" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "NEEDS_RAW_EVIDENCE", + "valid": true + }, + "id": "ambiguous-evidence-escalation", + "packet": { + "decision_evidence": { + "counts": { + "passed": 0, + "failed": 0, + "skipped": 0, + "total": 0 + }, + "disposition": "NEEDS_RAW_EVIDENCE", + "failures": [], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 0, + "interrupted": false + }, + "reason_codes": [ + "UNCLASSIFIED_EVIDENCE" + ], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "indeterminate", + "timeouts": [], + "unclassified_evidence": [ + { + "category": "unclassified", + "line": 1, + "sha256": "sha256:e041a8541dcc2dd3290e5d85cc2f200c385eb02552a5e13a613e73658e398e90", + "stream": "stdout", + "text": "this is not TAP" + }, + { + "category": "unclassified", + "line": 2, + "sha256": "sha256:c2a0912d4ebfeb355970b53735b6431a7cfc83bc2e674854760dd2e0d24a5e44", + "stream": "stdout", + "text": "not okay maybe FAIL" + } + ], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:0614f60cadb064fb8af6215cf804498049d57bea091b77157a4edde84cb00e2a", + "max_output_bytes": null, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:0b83fa88bc0a2593f2fded10cdfc09ad05f3cbbcad382872ab18ed1e8ea7f4a4", + "measurements": { + "original_bytes": 36, + "original_event_count": 2, + "reduced_bytes": 2130, + "retained_evidence_count": 2, + "suppressed_evidence_count": 0 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": null + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": true, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": false + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": false, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance", + "unclassified" + ], + "event_count": 2 + }, + "semantic_payload_hash": "sha256:bf4b58442ea401cf263c39f723953c92168231c0ca89673b96f72ce782ebcddf", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 36, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": {}, + "event_count": 0 + }, + "unclassified_evidence_present": true + } + }, + "producer_fixture": "edge/malformed-output", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 0, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "this is not TAP\nnot okay maybe FAIL\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "NEEDS_RAW_EVIDENCE", + "valid": true + }, + "id": "ceiling-triggered-escalation", + "packet": { + "decision_evidence": { + "counts": { + "passed": 0, + "failed": 1, + "skipped": 0, + "total": 1 + }, + "disposition": "NEEDS_RAW_EVIDENCE", + "failures": [ + { + "identity": "oversized-critical" + } + ], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 1, + "interrupted": false + }, + "reason_codes": [ + "PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED" + ], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "failed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:fac3bbe1a598e2699744a561adbc0b6ded1df9604e1d7977920037ea68997423", + "max_output_bytes": 3500, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:5c702b7f86610b695562ce075812bb2415f9d9eba933749d17972fc524ed105a", + "measurements": { + "original_bytes": 32121, + "original_event_count": 9, + "reduced_bytes": 1926, + "retained_evidence_count": 0, + "suppressed_evidence_count": 9 + }, + "payload_limit": { + "affected": true, + "honored": true, + "requested_bytes": 3500 + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": true, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": false, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 0 + }, + "semantic_payload_hash": "sha256:b71cc862f79684b18a6b8996b404e61c602224c64fb1230ca5e97c89f957b4dd", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 32121, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "failed_test": 1, + "failure_message": 1, + "structure": 2 + }, + "event_count": 9 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "payload/critical-too-large", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 1, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nnot ok 1 - oversized-critical\n message: criticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcriticalcritical\n1..1\n# tests 1\n# pass 0\n# fail 1\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + }, + { + "expected": { + "classification": "VALID", + "sufficiency": "SUFFICIENT", + "valid": true + }, + "id": "exact-payload-boundary", + "packet": { + "decision_evidence": { + "counts": { + "passed": 1500, + "failed": 0, + "skipped": 0, + "total": 1500 + }, + "disposition": "SUFFICIENT", + "failures": [], + "fatal_errors": [], + "process": { + "duration_ms": 12.5, + "exit_code": 0, + "interrupted": false + }, + "reason_codes": [], + "source": { + "id": "synthetic-suite", + "run_id": "run-001" + }, + "status": "passed", + "timeouts": [], + "unclassified_evidence": [], + "warnings": [] + }, + "operation_id": "op-synthetic-001", + "protocol_version": "opsle.context-firewall.evidence-packet/v1", + "receipt": { + "configuration": { + "identity": "sha256:8e3f0537826d358aa26f585f336fdd011fdd8d086787b74f6f8a6e3f83aae534", + "max_output_bytes": 1846, + "policy_revision": "tap-subset-policy/v1" + }, + "input_hash": "sha256:82ea05bab3fc9551830551372723ab484c5893f93b9ee88bbfaf9a1e9cf42abc", + "measurements": { + "original_bytes": 28981, + "original_event_count": 1507, + "reduced_bytes": 1846, + "retained_evidence_count": 0, + "suppressed_evidence_count": 1507 + }, + "payload_limit": { + "affected": false, + "honored": true, + "requested_bytes": 1846 + }, + "raw_evidence": { + "destroyed_by_reducer": false, + "escalation_available": true, + "escalation_required": false, + "reference": "artifact://synthetic/run-001.tap", + "source_evidence_disposition": "CALLER_REFERENCE_SUPPLIED", + "suppressed_from_model_context": true + }, + "receipt_version": 1, + "reducer": { + "name": "@opsle/context-firewall/test-output", + "version": "0.2.0" + }, + "reduction_complete": true, + "retained": { + "categories": [ + "aggregate_counts", + "process_status", + "run_verdict", + "stream_provenance" + ], + "event_count": 0 + }, + "semantic_payload_hash": "sha256:d99849b21430365b8b3aefd974d26d625092874054c8c09d1eea69e84e9a4e49", + "source": { + "id": "synthetic-suite", + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "run_id": "run-001", + "streams": [ + { + "byte_count": 28981, + "name": "stdout" + } + ] + }, + "suppressed": { + "categories": { + "aggregate_source": 4, + "duration_source": 1, + "structure": 2, + "successful_test": 1500 + }, + "event_count": 1507 + }, + "unclassified_evidence_present": false + } + }, + "producer_fixture": "payload/exact-boundary", + "source_input": { + "protocol_version": "opsle.context-firewall.test-run-input/v1", + "operation_id": "op-synthetic-001", + "source": { + "id": "synthetic-suite", + "run_id": "run-001", + "raw_evidence_ref": "artifact://synthetic/run-001.tap" + }, + "process": { + "exit_code": 0, + "duration_ms": 12.5, + "interrupted": false + }, + "streams": [ + { + "name": "stdout", + "encoding": "utf8", + "data": "TAP version 13\nok 1 - case-0001\nok 2 - case-0002\nok 3 - case-0003\nok 4 - case-0004\nok 5 - case-0005\nok 6 - case-0006\nok 7 - case-0007\nok 8 - case-0008\nok 9 - case-0009\nok 10 - case-0010\nok 11 - case-0011\nok 12 - case-0012\nok 13 - case-0013\nok 14 - case-0014\nok 15 - case-0015\nok 16 - case-0016\nok 17 - case-0017\nok 18 - case-0018\nok 19 - case-0019\nok 20 - case-0020\nok 21 - case-0021\nok 22 - case-0022\nok 23 - case-0023\nok 24 - case-0024\nok 25 - case-0025\nok 26 - case-0026\nok 27 - case-0027\nok 28 - case-0028\nok 29 - case-0029\nok 30 - case-0030\nok 31 - case-0031\nok 32 - case-0032\nok 33 - case-0033\nok 34 - case-0034\nok 35 - case-0035\nok 36 - case-0036\nok 37 - case-0037\nok 38 - case-0038\nok 39 - case-0039\nok 40 - case-0040\nok 41 - case-0041\nok 42 - case-0042\nok 43 - case-0043\nok 44 - case-0044\nok 45 - case-0045\nok 46 - case-0046\nok 47 - case-0047\nok 48 - case-0048\nok 49 - case-0049\nok 50 - case-0050\nok 51 - case-0051\nok 52 - case-0052\nok 53 - case-0053\nok 54 - case-0054\nok 55 - case-0055\nok 56 - case-0056\nok 57 - case-0057\nok 58 - case-0058\nok 59 - case-0059\nok 60 - case-0060\nok 61 - case-0061\nok 62 - case-0062\nok 63 - case-0063\nok 64 - case-0064\nok 65 - case-0065\nok 66 - case-0066\nok 67 - case-0067\nok 68 - case-0068\nok 69 - case-0069\nok 70 - case-0070\nok 71 - case-0071\nok 72 - case-0072\nok 73 - case-0073\nok 74 - case-0074\nok 75 - case-0075\nok 76 - case-0076\nok 77 - case-0077\nok 78 - case-0078\nok 79 - case-0079\nok 80 - case-0080\nok 81 - case-0081\nok 82 - case-0082\nok 83 - case-0083\nok 84 - case-0084\nok 85 - case-0085\nok 86 - case-0086\nok 87 - case-0087\nok 88 - case-0088\nok 89 - case-0089\nok 90 - case-0090\nok 91 - case-0091\nok 92 - case-0092\nok 93 - case-0093\nok 94 - case-0094\nok 95 - case-0095\nok 96 - case-0096\nok 97 - case-0097\nok 98 - case-0098\nok 99 - case-0099\nok 100 - case-0100\nok 101 - case-0101\nok 102 - case-0102\nok 103 - case-0103\nok 104 - case-0104\nok 105 - case-0105\nok 106 - case-0106\nok 107 - case-0107\nok 108 - case-0108\nok 109 - case-0109\nok 110 - case-0110\nok 111 - case-0111\nok 112 - case-0112\nok 113 - case-0113\nok 114 - case-0114\nok 115 - case-0115\nok 116 - case-0116\nok 117 - case-0117\nok 118 - case-0118\nok 119 - case-0119\nok 120 - case-0120\nok 121 - case-0121\nok 122 - case-0122\nok 123 - case-0123\nok 124 - case-0124\nok 125 - case-0125\nok 126 - case-0126\nok 127 - case-0127\nok 128 - case-0128\nok 129 - case-0129\nok 130 - case-0130\nok 131 - case-0131\nok 132 - case-0132\nok 133 - case-0133\nok 134 - case-0134\nok 135 - case-0135\nok 136 - case-0136\nok 137 - case-0137\nok 138 - case-0138\nok 139 - case-0139\nok 140 - case-0140\nok 141 - case-0141\nok 142 - case-0142\nok 143 - case-0143\nok 144 - case-0144\nok 145 - case-0145\nok 146 - case-0146\nok 147 - case-0147\nok 148 - case-0148\nok 149 - case-0149\nok 150 - case-0150\nok 151 - case-0151\nok 152 - case-0152\nok 153 - case-0153\nok 154 - case-0154\nok 155 - case-0155\nok 156 - case-0156\nok 157 - case-0157\nok 158 - case-0158\nok 159 - case-0159\nok 160 - case-0160\nok 161 - case-0161\nok 162 - case-0162\nok 163 - case-0163\nok 164 - case-0164\nok 165 - case-0165\nok 166 - case-0166\nok 167 - case-0167\nok 168 - case-0168\nok 169 - case-0169\nok 170 - case-0170\nok 171 - case-0171\nok 172 - case-0172\nok 173 - case-0173\nok 174 - case-0174\nok 175 - case-0175\nok 176 - case-0176\nok 177 - case-0177\nok 178 - case-0178\nok 179 - case-0179\nok 180 - case-0180\nok 181 - case-0181\nok 182 - case-0182\nok 183 - case-0183\nok 184 - case-0184\nok 185 - case-0185\nok 186 - case-0186\nok 187 - case-0187\nok 188 - case-0188\nok 189 - case-0189\nok 190 - case-0190\nok 191 - case-0191\nok 192 - case-0192\nok 193 - case-0193\nok 194 - case-0194\nok 195 - case-0195\nok 196 - case-0196\nok 197 - case-0197\nok 198 - case-0198\nok 199 - case-0199\nok 200 - case-0200\nok 201 - case-0201\nok 202 - case-0202\nok 203 - case-0203\nok 204 - case-0204\nok 205 - case-0205\nok 206 - case-0206\nok 207 - case-0207\nok 208 - case-0208\nok 209 - case-0209\nok 210 - case-0210\nok 211 - case-0211\nok 212 - case-0212\nok 213 - case-0213\nok 214 - case-0214\nok 215 - case-0215\nok 216 - case-0216\nok 217 - case-0217\nok 218 - case-0218\nok 219 - case-0219\nok 220 - case-0220\nok 221 - case-0221\nok 222 - case-0222\nok 223 - case-0223\nok 224 - case-0224\nok 225 - case-0225\nok 226 - case-0226\nok 227 - case-0227\nok 228 - case-0228\nok 229 - case-0229\nok 230 - case-0230\nok 231 - case-0231\nok 232 - case-0232\nok 233 - case-0233\nok 234 - case-0234\nok 235 - case-0235\nok 236 - case-0236\nok 237 - case-0237\nok 238 - case-0238\nok 239 - case-0239\nok 240 - case-0240\nok 241 - case-0241\nok 242 - case-0242\nok 243 - case-0243\nok 244 - case-0244\nok 245 - case-0245\nok 246 - case-0246\nok 247 - case-0247\nok 248 - case-0248\nok 249 - case-0249\nok 250 - case-0250\nok 251 - case-0251\nok 252 - case-0252\nok 253 - case-0253\nok 254 - case-0254\nok 255 - case-0255\nok 256 - case-0256\nok 257 - case-0257\nok 258 - case-0258\nok 259 - case-0259\nok 260 - case-0260\nok 261 - case-0261\nok 262 - case-0262\nok 263 - case-0263\nok 264 - case-0264\nok 265 - case-0265\nok 266 - case-0266\nok 267 - case-0267\nok 268 - case-0268\nok 269 - case-0269\nok 270 - case-0270\nok 271 - case-0271\nok 272 - case-0272\nok 273 - case-0273\nok 274 - case-0274\nok 275 - case-0275\nok 276 - case-0276\nok 277 - case-0277\nok 278 - case-0278\nok 279 - case-0279\nok 280 - case-0280\nok 281 - case-0281\nok 282 - case-0282\nok 283 - case-0283\nok 284 - case-0284\nok 285 - case-0285\nok 286 - case-0286\nok 287 - case-0287\nok 288 - case-0288\nok 289 - case-0289\nok 290 - case-0290\nok 291 - case-0291\nok 292 - case-0292\nok 293 - case-0293\nok 294 - case-0294\nok 295 - case-0295\nok 296 - case-0296\nok 297 - case-0297\nok 298 - case-0298\nok 299 - case-0299\nok 300 - case-0300\nok 301 - case-0301\nok 302 - case-0302\nok 303 - case-0303\nok 304 - case-0304\nok 305 - case-0305\nok 306 - case-0306\nok 307 - case-0307\nok 308 - case-0308\nok 309 - case-0309\nok 310 - case-0310\nok 311 - case-0311\nok 312 - case-0312\nok 313 - case-0313\nok 314 - case-0314\nok 315 - case-0315\nok 316 - case-0316\nok 317 - case-0317\nok 318 - case-0318\nok 319 - case-0319\nok 320 - case-0320\nok 321 - case-0321\nok 322 - case-0322\nok 323 - case-0323\nok 324 - case-0324\nok 325 - case-0325\nok 326 - case-0326\nok 327 - case-0327\nok 328 - case-0328\nok 329 - case-0329\nok 330 - case-0330\nok 331 - case-0331\nok 332 - case-0332\nok 333 - case-0333\nok 334 - case-0334\nok 335 - case-0335\nok 336 - case-0336\nok 337 - case-0337\nok 338 - case-0338\nok 339 - case-0339\nok 340 - case-0340\nok 341 - case-0341\nok 342 - case-0342\nok 343 - case-0343\nok 344 - case-0344\nok 345 - case-0345\nok 346 - case-0346\nok 347 - case-0347\nok 348 - case-0348\nok 349 - case-0349\nok 350 - case-0350\nok 351 - case-0351\nok 352 - case-0352\nok 353 - case-0353\nok 354 - case-0354\nok 355 - case-0355\nok 356 - case-0356\nok 357 - case-0357\nok 358 - case-0358\nok 359 - case-0359\nok 360 - case-0360\nok 361 - case-0361\nok 362 - case-0362\nok 363 - case-0363\nok 364 - case-0364\nok 365 - case-0365\nok 366 - case-0366\nok 367 - case-0367\nok 368 - case-0368\nok 369 - case-0369\nok 370 - case-0370\nok 371 - case-0371\nok 372 - case-0372\nok 373 - case-0373\nok 374 - case-0374\nok 375 - case-0375\nok 376 - case-0376\nok 377 - case-0377\nok 378 - case-0378\nok 379 - case-0379\nok 380 - case-0380\nok 381 - case-0381\nok 382 - case-0382\nok 383 - case-0383\nok 384 - case-0384\nok 385 - case-0385\nok 386 - case-0386\nok 387 - case-0387\nok 388 - case-0388\nok 389 - case-0389\nok 390 - case-0390\nok 391 - case-0391\nok 392 - case-0392\nok 393 - case-0393\nok 394 - case-0394\nok 395 - case-0395\nok 396 - case-0396\nok 397 - case-0397\nok 398 - case-0398\nok 399 - case-0399\nok 400 - case-0400\nok 401 - case-0401\nok 402 - case-0402\nok 403 - case-0403\nok 404 - case-0404\nok 405 - case-0405\nok 406 - case-0406\nok 407 - case-0407\nok 408 - case-0408\nok 409 - case-0409\nok 410 - case-0410\nok 411 - case-0411\nok 412 - case-0412\nok 413 - case-0413\nok 414 - case-0414\nok 415 - case-0415\nok 416 - case-0416\nok 417 - case-0417\nok 418 - case-0418\nok 419 - case-0419\nok 420 - case-0420\nok 421 - case-0421\nok 422 - case-0422\nok 423 - case-0423\nok 424 - case-0424\nok 425 - case-0425\nok 426 - case-0426\nok 427 - case-0427\nok 428 - case-0428\nok 429 - case-0429\nok 430 - case-0430\nok 431 - case-0431\nok 432 - case-0432\nok 433 - case-0433\nok 434 - case-0434\nok 435 - case-0435\nok 436 - case-0436\nok 437 - case-0437\nok 438 - case-0438\nok 439 - case-0439\nok 440 - case-0440\nok 441 - case-0441\nok 442 - case-0442\nok 443 - case-0443\nok 444 - case-0444\nok 445 - case-0445\nok 446 - case-0446\nok 447 - case-0447\nok 448 - case-0448\nok 449 - case-0449\nok 450 - case-0450\nok 451 - case-0451\nok 452 - case-0452\nok 453 - case-0453\nok 454 - case-0454\nok 455 - case-0455\nok 456 - case-0456\nok 457 - case-0457\nok 458 - case-0458\nok 459 - case-0459\nok 460 - case-0460\nok 461 - case-0461\nok 462 - case-0462\nok 463 - case-0463\nok 464 - case-0464\nok 465 - case-0465\nok 466 - case-0466\nok 467 - case-0467\nok 468 - case-0468\nok 469 - case-0469\nok 470 - case-0470\nok 471 - case-0471\nok 472 - case-0472\nok 473 - case-0473\nok 474 - case-0474\nok 475 - case-0475\nok 476 - case-0476\nok 477 - case-0477\nok 478 - case-0478\nok 479 - case-0479\nok 480 - case-0480\nok 481 - case-0481\nok 482 - case-0482\nok 483 - case-0483\nok 484 - case-0484\nok 485 - case-0485\nok 486 - case-0486\nok 487 - case-0487\nok 488 - case-0488\nok 489 - case-0489\nok 490 - case-0490\nok 491 - case-0491\nok 492 - case-0492\nok 493 - case-0493\nok 494 - case-0494\nok 495 - case-0495\nok 496 - case-0496\nok 497 - case-0497\nok 498 - case-0498\nok 499 - case-0499\nok 500 - case-0500\nok 501 - case-0501\nok 502 - case-0502\nok 503 - case-0503\nok 504 - case-0504\nok 505 - case-0505\nok 506 - case-0506\nok 507 - case-0507\nok 508 - case-0508\nok 509 - case-0509\nok 510 - case-0510\nok 511 - case-0511\nok 512 - case-0512\nok 513 - case-0513\nok 514 - case-0514\nok 515 - case-0515\nok 516 - case-0516\nok 517 - case-0517\nok 518 - case-0518\nok 519 - case-0519\nok 520 - case-0520\nok 521 - case-0521\nok 522 - case-0522\nok 523 - case-0523\nok 524 - case-0524\nok 525 - case-0525\nok 526 - case-0526\nok 527 - case-0527\nok 528 - case-0528\nok 529 - case-0529\nok 530 - case-0530\nok 531 - case-0531\nok 532 - case-0532\nok 533 - case-0533\nok 534 - case-0534\nok 535 - case-0535\nok 536 - case-0536\nok 537 - case-0537\nok 538 - case-0538\nok 539 - case-0539\nok 540 - case-0540\nok 541 - case-0541\nok 542 - case-0542\nok 543 - case-0543\nok 544 - case-0544\nok 545 - case-0545\nok 546 - case-0546\nok 547 - case-0547\nok 548 - case-0548\nok 549 - case-0549\nok 550 - case-0550\nok 551 - case-0551\nok 552 - case-0552\nok 553 - case-0553\nok 554 - case-0554\nok 555 - case-0555\nok 556 - case-0556\nok 557 - case-0557\nok 558 - case-0558\nok 559 - case-0559\nok 560 - case-0560\nok 561 - case-0561\nok 562 - case-0562\nok 563 - case-0563\nok 564 - case-0564\nok 565 - case-0565\nok 566 - case-0566\nok 567 - case-0567\nok 568 - case-0568\nok 569 - case-0569\nok 570 - case-0570\nok 571 - case-0571\nok 572 - case-0572\nok 573 - case-0573\nok 574 - case-0574\nok 575 - case-0575\nok 576 - case-0576\nok 577 - case-0577\nok 578 - case-0578\nok 579 - case-0579\nok 580 - case-0580\nok 581 - case-0581\nok 582 - case-0582\nok 583 - case-0583\nok 584 - case-0584\nok 585 - case-0585\nok 586 - case-0586\nok 587 - case-0587\nok 588 - case-0588\nok 589 - case-0589\nok 590 - case-0590\nok 591 - case-0591\nok 592 - case-0592\nok 593 - case-0593\nok 594 - case-0594\nok 595 - case-0595\nok 596 - case-0596\nok 597 - case-0597\nok 598 - case-0598\nok 599 - case-0599\nok 600 - case-0600\nok 601 - case-0601\nok 602 - case-0602\nok 603 - case-0603\nok 604 - case-0604\nok 605 - case-0605\nok 606 - case-0606\nok 607 - case-0607\nok 608 - case-0608\nok 609 - case-0609\nok 610 - case-0610\nok 611 - case-0611\nok 612 - case-0612\nok 613 - case-0613\nok 614 - case-0614\nok 615 - case-0615\nok 616 - case-0616\nok 617 - case-0617\nok 618 - case-0618\nok 619 - case-0619\nok 620 - case-0620\nok 621 - case-0621\nok 622 - case-0622\nok 623 - case-0623\nok 624 - case-0624\nok 625 - case-0625\nok 626 - case-0626\nok 627 - case-0627\nok 628 - case-0628\nok 629 - case-0629\nok 630 - case-0630\nok 631 - case-0631\nok 632 - case-0632\nok 633 - case-0633\nok 634 - case-0634\nok 635 - case-0635\nok 636 - case-0636\nok 637 - case-0637\nok 638 - case-0638\nok 639 - case-0639\nok 640 - case-0640\nok 641 - case-0641\nok 642 - case-0642\nok 643 - case-0643\nok 644 - case-0644\nok 645 - case-0645\nok 646 - case-0646\nok 647 - case-0647\nok 648 - case-0648\nok 649 - case-0649\nok 650 - case-0650\nok 651 - case-0651\nok 652 - case-0652\nok 653 - case-0653\nok 654 - case-0654\nok 655 - case-0655\nok 656 - case-0656\nok 657 - case-0657\nok 658 - case-0658\nok 659 - case-0659\nok 660 - case-0660\nok 661 - case-0661\nok 662 - case-0662\nok 663 - case-0663\nok 664 - case-0664\nok 665 - case-0665\nok 666 - case-0666\nok 667 - case-0667\nok 668 - case-0668\nok 669 - case-0669\nok 670 - case-0670\nok 671 - case-0671\nok 672 - case-0672\nok 673 - case-0673\nok 674 - case-0674\nok 675 - case-0675\nok 676 - case-0676\nok 677 - case-0677\nok 678 - case-0678\nok 679 - case-0679\nok 680 - case-0680\nok 681 - case-0681\nok 682 - case-0682\nok 683 - case-0683\nok 684 - case-0684\nok 685 - case-0685\nok 686 - case-0686\nok 687 - case-0687\nok 688 - case-0688\nok 689 - case-0689\nok 690 - case-0690\nok 691 - case-0691\nok 692 - case-0692\nok 693 - case-0693\nok 694 - case-0694\nok 695 - case-0695\nok 696 - case-0696\nok 697 - case-0697\nok 698 - case-0698\nok 699 - case-0699\nok 700 - case-0700\nok 701 - case-0701\nok 702 - case-0702\nok 703 - case-0703\nok 704 - case-0704\nok 705 - case-0705\nok 706 - case-0706\nok 707 - case-0707\nok 708 - case-0708\nok 709 - case-0709\nok 710 - case-0710\nok 711 - case-0711\nok 712 - case-0712\nok 713 - case-0713\nok 714 - case-0714\nok 715 - case-0715\nok 716 - case-0716\nok 717 - case-0717\nok 718 - case-0718\nok 719 - case-0719\nok 720 - case-0720\nok 721 - case-0721\nok 722 - case-0722\nok 723 - case-0723\nok 724 - case-0724\nok 725 - case-0725\nok 726 - case-0726\nok 727 - case-0727\nok 728 - case-0728\nok 729 - case-0729\nok 730 - case-0730\nok 731 - case-0731\nok 732 - case-0732\nok 733 - case-0733\nok 734 - case-0734\nok 735 - case-0735\nok 736 - case-0736\nok 737 - case-0737\nok 738 - case-0738\nok 739 - case-0739\nok 740 - case-0740\nok 741 - case-0741\nok 742 - case-0742\nok 743 - case-0743\nok 744 - case-0744\nok 745 - case-0745\nok 746 - case-0746\nok 747 - case-0747\nok 748 - case-0748\nok 749 - case-0749\nok 750 - case-0750\nok 751 - case-0751\nok 752 - case-0752\nok 753 - case-0753\nok 754 - case-0754\nok 755 - case-0755\nok 756 - case-0756\nok 757 - case-0757\nok 758 - case-0758\nok 759 - case-0759\nok 760 - case-0760\nok 761 - case-0761\nok 762 - case-0762\nok 763 - case-0763\nok 764 - case-0764\nok 765 - case-0765\nok 766 - case-0766\nok 767 - case-0767\nok 768 - case-0768\nok 769 - case-0769\nok 770 - case-0770\nok 771 - case-0771\nok 772 - case-0772\nok 773 - case-0773\nok 774 - case-0774\nok 775 - case-0775\nok 776 - case-0776\nok 777 - case-0777\nok 778 - case-0778\nok 779 - case-0779\nok 780 - case-0780\nok 781 - case-0781\nok 782 - case-0782\nok 783 - case-0783\nok 784 - case-0784\nok 785 - case-0785\nok 786 - case-0786\nok 787 - case-0787\nok 788 - case-0788\nok 789 - case-0789\nok 790 - case-0790\nok 791 - case-0791\nok 792 - case-0792\nok 793 - case-0793\nok 794 - case-0794\nok 795 - case-0795\nok 796 - case-0796\nok 797 - case-0797\nok 798 - case-0798\nok 799 - case-0799\nok 800 - case-0800\nok 801 - case-0801\nok 802 - case-0802\nok 803 - case-0803\nok 804 - case-0804\nok 805 - case-0805\nok 806 - case-0806\nok 807 - case-0807\nok 808 - case-0808\nok 809 - case-0809\nok 810 - case-0810\nok 811 - case-0811\nok 812 - case-0812\nok 813 - case-0813\nok 814 - case-0814\nok 815 - case-0815\nok 816 - case-0816\nok 817 - case-0817\nok 818 - case-0818\nok 819 - case-0819\nok 820 - case-0820\nok 821 - case-0821\nok 822 - case-0822\nok 823 - case-0823\nok 824 - case-0824\nok 825 - case-0825\nok 826 - case-0826\nok 827 - case-0827\nok 828 - case-0828\nok 829 - case-0829\nok 830 - case-0830\nok 831 - case-0831\nok 832 - case-0832\nok 833 - case-0833\nok 834 - case-0834\nok 835 - case-0835\nok 836 - case-0836\nok 837 - case-0837\nok 838 - case-0838\nok 839 - case-0839\nok 840 - case-0840\nok 841 - case-0841\nok 842 - case-0842\nok 843 - case-0843\nok 844 - case-0844\nok 845 - case-0845\nok 846 - case-0846\nok 847 - case-0847\nok 848 - case-0848\nok 849 - case-0849\nok 850 - case-0850\nok 851 - case-0851\nok 852 - case-0852\nok 853 - case-0853\nok 854 - case-0854\nok 855 - case-0855\nok 856 - case-0856\nok 857 - case-0857\nok 858 - case-0858\nok 859 - case-0859\nok 860 - case-0860\nok 861 - case-0861\nok 862 - case-0862\nok 863 - case-0863\nok 864 - case-0864\nok 865 - case-0865\nok 866 - case-0866\nok 867 - case-0867\nok 868 - case-0868\nok 869 - case-0869\nok 870 - case-0870\nok 871 - case-0871\nok 872 - case-0872\nok 873 - case-0873\nok 874 - case-0874\nok 875 - case-0875\nok 876 - case-0876\nok 877 - case-0877\nok 878 - case-0878\nok 879 - case-0879\nok 880 - case-0880\nok 881 - case-0881\nok 882 - case-0882\nok 883 - case-0883\nok 884 - case-0884\nok 885 - case-0885\nok 886 - case-0886\nok 887 - case-0887\nok 888 - case-0888\nok 889 - case-0889\nok 890 - case-0890\nok 891 - case-0891\nok 892 - case-0892\nok 893 - case-0893\nok 894 - case-0894\nok 895 - case-0895\nok 896 - case-0896\nok 897 - case-0897\nok 898 - case-0898\nok 899 - case-0899\nok 900 - case-0900\nok 901 - case-0901\nok 902 - case-0902\nok 903 - case-0903\nok 904 - case-0904\nok 905 - case-0905\nok 906 - case-0906\nok 907 - case-0907\nok 908 - case-0908\nok 909 - case-0909\nok 910 - case-0910\nok 911 - case-0911\nok 912 - case-0912\nok 913 - case-0913\nok 914 - case-0914\nok 915 - case-0915\nok 916 - case-0916\nok 917 - case-0917\nok 918 - case-0918\nok 919 - case-0919\nok 920 - case-0920\nok 921 - case-0921\nok 922 - case-0922\nok 923 - case-0923\nok 924 - case-0924\nok 925 - case-0925\nok 926 - case-0926\nok 927 - case-0927\nok 928 - case-0928\nok 929 - case-0929\nok 930 - case-0930\nok 931 - case-0931\nok 932 - case-0932\nok 933 - case-0933\nok 934 - case-0934\nok 935 - case-0935\nok 936 - case-0936\nok 937 - case-0937\nok 938 - case-0938\nok 939 - case-0939\nok 940 - case-0940\nok 941 - case-0941\nok 942 - case-0942\nok 943 - case-0943\nok 944 - case-0944\nok 945 - case-0945\nok 946 - case-0946\nok 947 - case-0947\nok 948 - case-0948\nok 949 - case-0949\nok 950 - case-0950\nok 951 - case-0951\nok 952 - case-0952\nok 953 - case-0953\nok 954 - case-0954\nok 955 - case-0955\nok 956 - case-0956\nok 957 - case-0957\nok 958 - case-0958\nok 959 - case-0959\nok 960 - case-0960\nok 961 - case-0961\nok 962 - case-0962\nok 963 - case-0963\nok 964 - case-0964\nok 965 - case-0965\nok 966 - case-0966\nok 967 - case-0967\nok 968 - case-0968\nok 969 - case-0969\nok 970 - case-0970\nok 971 - case-0971\nok 972 - case-0972\nok 973 - case-0973\nok 974 - case-0974\nok 975 - case-0975\nok 976 - case-0976\nok 977 - case-0977\nok 978 - case-0978\nok 979 - case-0979\nok 980 - case-0980\nok 981 - case-0981\nok 982 - case-0982\nok 983 - case-0983\nok 984 - case-0984\nok 985 - case-0985\nok 986 - case-0986\nok 987 - case-0987\nok 988 - case-0988\nok 989 - case-0989\nok 990 - case-0990\nok 991 - case-0991\nok 992 - case-0992\nok 993 - case-0993\nok 994 - case-0994\nok 995 - case-0995\nok 996 - case-0996\nok 997 - case-0997\nok 998 - case-0998\nok 999 - case-0999\nok 1000 - case-1000\nok 1001 - case-1001\nok 1002 - case-1002\nok 1003 - case-1003\nok 1004 - case-1004\nok 1005 - case-1005\nok 1006 - case-1006\nok 1007 - case-1007\nok 1008 - case-1008\nok 1009 - case-1009\nok 1010 - case-1010\nok 1011 - case-1011\nok 1012 - case-1012\nok 1013 - case-1013\nok 1014 - case-1014\nok 1015 - case-1015\nok 1016 - case-1016\nok 1017 - case-1017\nok 1018 - case-1018\nok 1019 - case-1019\nok 1020 - case-1020\nok 1021 - case-1021\nok 1022 - case-1022\nok 1023 - case-1023\nok 1024 - case-1024\nok 1025 - case-1025\nok 1026 - case-1026\nok 1027 - case-1027\nok 1028 - case-1028\nok 1029 - case-1029\nok 1030 - case-1030\nok 1031 - case-1031\nok 1032 - case-1032\nok 1033 - case-1033\nok 1034 - case-1034\nok 1035 - case-1035\nok 1036 - case-1036\nok 1037 - case-1037\nok 1038 - case-1038\nok 1039 - case-1039\nok 1040 - case-1040\nok 1041 - case-1041\nok 1042 - case-1042\nok 1043 - case-1043\nok 1044 - case-1044\nok 1045 - case-1045\nok 1046 - case-1046\nok 1047 - case-1047\nok 1048 - case-1048\nok 1049 - case-1049\nok 1050 - case-1050\nok 1051 - case-1051\nok 1052 - case-1052\nok 1053 - case-1053\nok 1054 - case-1054\nok 1055 - case-1055\nok 1056 - case-1056\nok 1057 - case-1057\nok 1058 - case-1058\nok 1059 - case-1059\nok 1060 - case-1060\nok 1061 - case-1061\nok 1062 - case-1062\nok 1063 - case-1063\nok 1064 - case-1064\nok 1065 - case-1065\nok 1066 - case-1066\nok 1067 - case-1067\nok 1068 - case-1068\nok 1069 - case-1069\nok 1070 - case-1070\nok 1071 - case-1071\nok 1072 - case-1072\nok 1073 - case-1073\nok 1074 - case-1074\nok 1075 - case-1075\nok 1076 - case-1076\nok 1077 - case-1077\nok 1078 - case-1078\nok 1079 - case-1079\nok 1080 - case-1080\nok 1081 - case-1081\nok 1082 - case-1082\nok 1083 - case-1083\nok 1084 - case-1084\nok 1085 - case-1085\nok 1086 - case-1086\nok 1087 - case-1087\nok 1088 - case-1088\nok 1089 - case-1089\nok 1090 - case-1090\nok 1091 - case-1091\nok 1092 - case-1092\nok 1093 - case-1093\nok 1094 - case-1094\nok 1095 - case-1095\nok 1096 - case-1096\nok 1097 - case-1097\nok 1098 - case-1098\nok 1099 - case-1099\nok 1100 - case-1100\nok 1101 - case-1101\nok 1102 - case-1102\nok 1103 - case-1103\nok 1104 - case-1104\nok 1105 - case-1105\nok 1106 - case-1106\nok 1107 - case-1107\nok 1108 - case-1108\nok 1109 - case-1109\nok 1110 - case-1110\nok 1111 - case-1111\nok 1112 - case-1112\nok 1113 - case-1113\nok 1114 - case-1114\nok 1115 - case-1115\nok 1116 - case-1116\nok 1117 - case-1117\nok 1118 - case-1118\nok 1119 - case-1119\nok 1120 - case-1120\nok 1121 - case-1121\nok 1122 - case-1122\nok 1123 - case-1123\nok 1124 - case-1124\nok 1125 - case-1125\nok 1126 - case-1126\nok 1127 - case-1127\nok 1128 - case-1128\nok 1129 - case-1129\nok 1130 - case-1130\nok 1131 - case-1131\nok 1132 - case-1132\nok 1133 - case-1133\nok 1134 - case-1134\nok 1135 - case-1135\nok 1136 - case-1136\nok 1137 - case-1137\nok 1138 - case-1138\nok 1139 - case-1139\nok 1140 - case-1140\nok 1141 - case-1141\nok 1142 - case-1142\nok 1143 - case-1143\nok 1144 - case-1144\nok 1145 - case-1145\nok 1146 - case-1146\nok 1147 - case-1147\nok 1148 - case-1148\nok 1149 - case-1149\nok 1150 - case-1150\nok 1151 - case-1151\nok 1152 - case-1152\nok 1153 - case-1153\nok 1154 - case-1154\nok 1155 - case-1155\nok 1156 - case-1156\nok 1157 - case-1157\nok 1158 - case-1158\nok 1159 - case-1159\nok 1160 - case-1160\nok 1161 - case-1161\nok 1162 - case-1162\nok 1163 - case-1163\nok 1164 - case-1164\nok 1165 - case-1165\nok 1166 - case-1166\nok 1167 - case-1167\nok 1168 - case-1168\nok 1169 - case-1169\nok 1170 - case-1170\nok 1171 - case-1171\nok 1172 - case-1172\nok 1173 - case-1173\nok 1174 - case-1174\nok 1175 - case-1175\nok 1176 - case-1176\nok 1177 - case-1177\nok 1178 - case-1178\nok 1179 - case-1179\nok 1180 - case-1180\nok 1181 - case-1181\nok 1182 - case-1182\nok 1183 - case-1183\nok 1184 - case-1184\nok 1185 - case-1185\nok 1186 - case-1186\nok 1187 - case-1187\nok 1188 - case-1188\nok 1189 - case-1189\nok 1190 - case-1190\nok 1191 - case-1191\nok 1192 - case-1192\nok 1193 - case-1193\nok 1194 - case-1194\nok 1195 - case-1195\nok 1196 - case-1196\nok 1197 - case-1197\nok 1198 - case-1198\nok 1199 - case-1199\nok 1200 - case-1200\nok 1201 - case-1201\nok 1202 - case-1202\nok 1203 - case-1203\nok 1204 - case-1204\nok 1205 - case-1205\nok 1206 - case-1206\nok 1207 - case-1207\nok 1208 - case-1208\nok 1209 - case-1209\nok 1210 - case-1210\nok 1211 - case-1211\nok 1212 - case-1212\nok 1213 - case-1213\nok 1214 - case-1214\nok 1215 - case-1215\nok 1216 - case-1216\nok 1217 - case-1217\nok 1218 - case-1218\nok 1219 - case-1219\nok 1220 - case-1220\nok 1221 - case-1221\nok 1222 - case-1222\nok 1223 - case-1223\nok 1224 - case-1224\nok 1225 - case-1225\nok 1226 - case-1226\nok 1227 - case-1227\nok 1228 - case-1228\nok 1229 - case-1229\nok 1230 - case-1230\nok 1231 - case-1231\nok 1232 - case-1232\nok 1233 - case-1233\nok 1234 - case-1234\nok 1235 - case-1235\nok 1236 - case-1236\nok 1237 - case-1237\nok 1238 - case-1238\nok 1239 - case-1239\nok 1240 - case-1240\nok 1241 - case-1241\nok 1242 - case-1242\nok 1243 - case-1243\nok 1244 - case-1244\nok 1245 - case-1245\nok 1246 - case-1246\nok 1247 - case-1247\nok 1248 - case-1248\nok 1249 - case-1249\nok 1250 - case-1250\nok 1251 - case-1251\nok 1252 - case-1252\nok 1253 - case-1253\nok 1254 - case-1254\nok 1255 - case-1255\nok 1256 - case-1256\nok 1257 - case-1257\nok 1258 - case-1258\nok 1259 - case-1259\nok 1260 - case-1260\nok 1261 - case-1261\nok 1262 - case-1262\nok 1263 - case-1263\nok 1264 - case-1264\nok 1265 - case-1265\nok 1266 - case-1266\nok 1267 - case-1267\nok 1268 - case-1268\nok 1269 - case-1269\nok 1270 - case-1270\nok 1271 - case-1271\nok 1272 - case-1272\nok 1273 - case-1273\nok 1274 - case-1274\nok 1275 - case-1275\nok 1276 - case-1276\nok 1277 - case-1277\nok 1278 - case-1278\nok 1279 - case-1279\nok 1280 - case-1280\nok 1281 - case-1281\nok 1282 - case-1282\nok 1283 - case-1283\nok 1284 - case-1284\nok 1285 - case-1285\nok 1286 - case-1286\nok 1287 - case-1287\nok 1288 - case-1288\nok 1289 - case-1289\nok 1290 - case-1290\nok 1291 - case-1291\nok 1292 - case-1292\nok 1293 - case-1293\nok 1294 - case-1294\nok 1295 - case-1295\nok 1296 - case-1296\nok 1297 - case-1297\nok 1298 - case-1298\nok 1299 - case-1299\nok 1300 - case-1300\nok 1301 - case-1301\nok 1302 - case-1302\nok 1303 - case-1303\nok 1304 - case-1304\nok 1305 - case-1305\nok 1306 - case-1306\nok 1307 - case-1307\nok 1308 - case-1308\nok 1309 - case-1309\nok 1310 - case-1310\nok 1311 - case-1311\nok 1312 - case-1312\nok 1313 - case-1313\nok 1314 - case-1314\nok 1315 - case-1315\nok 1316 - case-1316\nok 1317 - case-1317\nok 1318 - case-1318\nok 1319 - case-1319\nok 1320 - case-1320\nok 1321 - case-1321\nok 1322 - case-1322\nok 1323 - case-1323\nok 1324 - case-1324\nok 1325 - case-1325\nok 1326 - case-1326\nok 1327 - case-1327\nok 1328 - case-1328\nok 1329 - case-1329\nok 1330 - case-1330\nok 1331 - case-1331\nok 1332 - case-1332\nok 1333 - case-1333\nok 1334 - case-1334\nok 1335 - case-1335\nok 1336 - case-1336\nok 1337 - case-1337\nok 1338 - case-1338\nok 1339 - case-1339\nok 1340 - case-1340\nok 1341 - case-1341\nok 1342 - case-1342\nok 1343 - case-1343\nok 1344 - case-1344\nok 1345 - case-1345\nok 1346 - case-1346\nok 1347 - case-1347\nok 1348 - case-1348\nok 1349 - case-1349\nok 1350 - case-1350\nok 1351 - case-1351\nok 1352 - case-1352\nok 1353 - case-1353\nok 1354 - case-1354\nok 1355 - case-1355\nok 1356 - case-1356\nok 1357 - case-1357\nok 1358 - case-1358\nok 1359 - case-1359\nok 1360 - case-1360\nok 1361 - case-1361\nok 1362 - case-1362\nok 1363 - case-1363\nok 1364 - case-1364\nok 1365 - case-1365\nok 1366 - case-1366\nok 1367 - case-1367\nok 1368 - case-1368\nok 1369 - case-1369\nok 1370 - case-1370\nok 1371 - case-1371\nok 1372 - case-1372\nok 1373 - case-1373\nok 1374 - case-1374\nok 1375 - case-1375\nok 1376 - case-1376\nok 1377 - case-1377\nok 1378 - case-1378\nok 1379 - case-1379\nok 1380 - case-1380\nok 1381 - case-1381\nok 1382 - case-1382\nok 1383 - case-1383\nok 1384 - case-1384\nok 1385 - case-1385\nok 1386 - case-1386\nok 1387 - case-1387\nok 1388 - case-1388\nok 1389 - case-1389\nok 1390 - case-1390\nok 1391 - case-1391\nok 1392 - case-1392\nok 1393 - case-1393\nok 1394 - case-1394\nok 1395 - case-1395\nok 1396 - case-1396\nok 1397 - case-1397\nok 1398 - case-1398\nok 1399 - case-1399\nok 1400 - case-1400\nok 1401 - case-1401\nok 1402 - case-1402\nok 1403 - case-1403\nok 1404 - case-1404\nok 1405 - case-1405\nok 1406 - case-1406\nok 1407 - case-1407\nok 1408 - case-1408\nok 1409 - case-1409\nok 1410 - case-1410\nok 1411 - case-1411\nok 1412 - case-1412\nok 1413 - case-1413\nok 1414 - case-1414\nok 1415 - case-1415\nok 1416 - case-1416\nok 1417 - case-1417\nok 1418 - case-1418\nok 1419 - case-1419\nok 1420 - case-1420\nok 1421 - case-1421\nok 1422 - case-1422\nok 1423 - case-1423\nok 1424 - case-1424\nok 1425 - case-1425\nok 1426 - case-1426\nok 1427 - case-1427\nok 1428 - case-1428\nok 1429 - case-1429\nok 1430 - case-1430\nok 1431 - case-1431\nok 1432 - case-1432\nok 1433 - case-1433\nok 1434 - case-1434\nok 1435 - case-1435\nok 1436 - case-1436\nok 1437 - case-1437\nok 1438 - case-1438\nok 1439 - case-1439\nok 1440 - case-1440\nok 1441 - case-1441\nok 1442 - case-1442\nok 1443 - case-1443\nok 1444 - case-1444\nok 1445 - case-1445\nok 1446 - case-1446\nok 1447 - case-1447\nok 1448 - case-1448\nok 1449 - case-1449\nok 1450 - case-1450\nok 1451 - case-1451\nok 1452 - case-1452\nok 1453 - case-1453\nok 1454 - case-1454\nok 1455 - case-1455\nok 1456 - case-1456\nok 1457 - case-1457\nok 1458 - case-1458\nok 1459 - case-1459\nok 1460 - case-1460\nok 1461 - case-1461\nok 1462 - case-1462\nok 1463 - case-1463\nok 1464 - case-1464\nok 1465 - case-1465\nok 1466 - case-1466\nok 1467 - case-1467\nok 1468 - case-1468\nok 1469 - case-1469\nok 1470 - case-1470\nok 1471 - case-1471\nok 1472 - case-1472\nok 1473 - case-1473\nok 1474 - case-1474\nok 1475 - case-1475\nok 1476 - case-1476\nok 1477 - case-1477\nok 1478 - case-1478\nok 1479 - case-1479\nok 1480 - case-1480\nok 1481 - case-1481\nok 1482 - case-1482\nok 1483 - case-1483\nok 1484 - case-1484\nok 1485 - case-1485\nok 1486 - case-1486\nok 1487 - case-1487\nok 1488 - case-1488\nok 1489 - case-1489\nok 1490 - case-1490\nok 1491 - case-1491\nok 1492 - case-1492\nok 1493 - case-1493\nok 1494 - case-1494\nok 1495 - case-1495\nok 1496 - case-1496\nok 1497 - case-1497\nok 1498 - case-1498\nok 1499 - case-1499\nok 1500 - case-1500\n1..1500\n# tests 1500\n# pass 1500\n# fail 0\n# skipped 0\n# duration_ms 12.5\n" + } + ] + } + } + ] +} diff --git a/package.json b/package.json index 2f75aef..b531be7 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,21 @@ { "name": "@opsle/decision-evidence-protocol", - "version": "0.1.0", + "version": "0.2.0", "private": true, "type": "module", + "bin": { + "decision-evidence": "./bin/decision-evidence.js" + }, + "exports": { + ".": "./src/index.js", + "./context-firewall-v1": "./src/context-firewall-v1.js" + }, "scripts": { - "test": "node --test" + "test": "node --test", + "conformance": "node ./bin/decision-evidence.js conformance", + "lint": "node --check src/*.js && node --check bin/*.js && node --check tools/*.js" + }, + "engines": { + "node": ">=20" } } diff --git a/src/README.md b/src/README.md index f5cba08..74c7d04 100644 --- a/src/README.md +++ b/src/README.md @@ -1,3 +1,11 @@ # Reference surface -This directory contains a dependency-free, deliberately narrow reference implementation. It demonstrates the interface and supports falsification; it is not a production package. +This directory contains dependency-free, deliberately narrow validators. + +- `validate.js` preserves the original generic envelope prototype. +- `context-firewall-v1.js` independently validates Context Firewall packet-v1 + receipts, hashes, source evidence, accounting, and escalation state. +- `conformance.js` runs the checked-in protocol vector corpus. + +The implementation supports falsification and interoperability. It is not a +production authorization or containment boundary. diff --git a/src/conformance.js b/src/conformance.js new file mode 100644 index 0000000..2d6bfe4 --- /dev/null +++ b/src/conformance.js @@ -0,0 +1,135 @@ +import { readFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; + +import { + canonicalJson, + validateContextFirewallPacket, +} from './context-firewall-v1.js'; + +function clone(value) { + return structuredClone(value); +} + +function pointerParts(pointer) { + if (pointer === '') return []; + if (!pointer.startsWith('/')) throw new TypeError(`invalid JSON pointer: ${pointer}`); + return pointer.slice(1).split('/').map((part) => part.replaceAll('~1', '/').replaceAll('~0', '~')); +} + +function parentAt(value, pointer) { + const parts = pointerParts(pointer); + const final = parts.pop(); + let parent = value; + for (const part of parts) parent = parent[part]; + return { parent, final }; +} + +function applyMutation(target, mutation) { + const { parent, final } = parentAt(target, mutation.path); + if (mutation.op === 'remove') delete parent[final]; + else if (mutation.op === 'replace') parent[final] = clone(mutation.value); + else if (mutation.op === 'add') parent[final] = clone(mutation.value); + else throw new TypeError(`unsupported mutation operation: ${mutation.op}`); +} + +function digestCanonical(value) { + const digest = createHash('sha256'); + digest.update(Buffer.from(canonicalJson(value), 'utf8')); + return `sha256:${digest.digest('hex')}`; +} + +function refreshDerivedPacketFields(packet, vector) { + if (vector.refresh_semantic_hash) { + packet.receipt.semantic_payload_hash = digestCanonical(packet.decision_evidence); + } + if (vector.refresh_configuration_hash) { + packet.receipt.configuration.identity = digestCanonical({ + max_output_bytes: packet.receipt.configuration.max_output_bytes, + policy_revision: packet.receipt.configuration.policy_revision, + reducer_version: packet.receipt.reducer.version, + }); + } + if (vector.refresh_reduced_bytes) { + let length = packet.receipt.measurements.reduced_bytes; + for (let iteration = 0; iteration < 12; iteration += 1) { + packet.receipt.measurements.reduced_bytes = length; + const next = Buffer.byteLength(`${canonicalJson(packet)}\n`, 'utf8'); + if (next === length) break; + length = next; + } + packet.receipt.measurements.reduced_bytes = length; + } +} + +function evaluateExpectation(result, expected) { + const actualCodes = result.violations.map((violation) => violation.code); + const missingCodes = (expected.violation_codes ?? []) + .filter((code) => !actualCodes.includes(code)); + return { + actual: { + classification: result.classification, + sufficiency: result.sufficiency, + valid: result.valid, + violation_codes: actualCodes, + }, + pass: result.valid === expected.valid + && result.classification === expected.classification + && result.sufficiency === expected.sufficiency + && missingCodes.length === 0, + }; +} + +export async function loadContextFirewallVectors(url = new URL( + '../fixtures/context-firewall-packet-v1/vectors.json', + import.meta.url, +)) { + return JSON.parse(await readFile(url, 'utf8')); +} + +export async function runContextFirewallConformance(options = {}) { + const vectors = options.vectors ?? await loadContextFirewallVectors(); + const baseById = new Map(vectors.valid_vectors.map((vector) => [vector.id, vector])); + const results = []; + + for (const vector of vectors.valid_vectors) { + const packetBytes = Buffer.from(`${canonicalJson(vector.packet)}\n`, 'utf8'); + const result = validateContextFirewallPacket(vector.packet, { + packetBytes, + sourceInput: vector.source_input, + }); + const evaluation = evaluateExpectation(result, vector.expected); + results.push({ + ...evaluation, + id: vector.id, + kind: 'valid', + }); + } + + for (const vector of vectors.invalid_vectors) { + const base = baseById.get(vector.base); + if (!base) throw new TypeError(`unknown base vector: ${vector.base}`); + const packet = clone(base.packet); + const sourceInput = clone(base.source_input); + for (const mutation of vector.packet_mutations ?? []) applyMutation(packet, mutation); + for (const mutation of vector.source_mutations ?? []) applyMutation(sourceInput, mutation); + refreshDerivedPacketFields(packet, vector); + const packetBytes = Buffer.from(`${canonicalJson(packet)}\n`, 'utf8'); + const result = validateContextFirewallPacket(packet, { packetBytes, sourceInput }); + const evaluation = evaluateExpectation(result, vector.expected); + results.push({ + ...evaluation, + id: vector.id, + kind: 'invalid', + }); + } + + return { + conformance: results.every((result) => result.pass) ? 'PASS' : 'FAIL', + context_firewall_revision: vectors.context_firewall_revision, + invalid_vector_count: vectors.invalid_vectors.length, + protocol_version: vectors.protocol_version, + results, + valid_vector_count: vectors.valid_vectors.length, + vector_count: results.length, + }; +} diff --git a/src/context-firewall-v1.js b/src/context-firewall-v1.js new file mode 100644 index 0000000..9704890 --- /dev/null +++ b/src/context-firewall-v1.js @@ -0,0 +1,1493 @@ +import { createHash } from 'node:crypto'; +import { TextDecoder } from 'node:util'; + +export const CONTEXT_FIREWALL_PACKET_PROTOCOL = + 'opsle.context-firewall.evidence-packet/v1'; +export const CONTEXT_FIREWALL_INPUT_PROTOCOL = + 'opsle.context-firewall.test-run-input/v1'; +export const CONTEXT_FIREWALL_REDUCER = + '@opsle/context-firewall/test-output'; +export const CONTEXT_FIREWALL_REDUCER_VERSION = '0.2.0'; +export const CONTEXT_FIREWALL_POLICY_REVISION = + 'tap-subset-policy/v1'; + +const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/; +const ANSI_PATTERN = /[\u001b\u009b][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*)?\u0007)|(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g; +const utf8Decoder = new TextDecoder('utf-8', { fatal: true }); +const streamRank = new Map([['stdout', 0], ['stderr', 1]]); + +const statuses = new Set(['passed', 'failed', 'indeterminate']); +const dispositions = new Set(['SUFFICIENT', 'NEEDS_RAW_EVIDENCE']); +const sourceEvidenceDispositions = new Set([ + 'CALLER_REFERENCE_SUPPLIED', + 'PRESERVATION_UNCONFIRMED', +]); +const sourceCategories = new Set([ + 'successful_test', + 'skipped_test', + 'failed_test', + 'failure_message', + 'assertion', + 'stack_trace', + 'failure_detail', + 'fatal_error', + 'timeout', + 'abnormal_warning', + 'aggregate_source', + 'duration_source', + 'structure', + 'informational', + 'blank', + 'unclassified', + 'unclassified_binary', +]); +const derivedCategories = [ + 'aggregate_counts', + 'process_status', + 'run_verdict', + 'stream_provenance', +]; +const reasonCodes = new Set([ + 'AGGREGATE_CONTRADICTION', + 'EXIT_STATUS_MISSING', + 'INTERRUPTED_OUTPUT', + 'MALFORMED_UTF8', + 'NONZERO_EXIT_WITHOUT_RECOGNIZED_FAILURE', + 'OPERATION_ID_MISSING', + 'PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED', + 'PAYLOAD_LIMIT_OMITTED_NONCRITICAL_TEXT', + 'RAW_EVIDENCE_REFERENCE_MISSING', + 'SOURCE_IDENTITY_MISSING', + 'UNCLASSIFIED_EVIDENCE', +]); +const payloadReasonCodes = new Set([ + 'PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED', + 'PAYLOAD_LIMIT_OMITTED_NONCRITICAL_TEXT', +]); +const indeterminateReasonCodes = new Set([ + 'AGGREGATE_CONTRADICTION', + 'EXIT_STATUS_MISSING', + 'INTERRUPTED_OUTPUT', + 'MALFORMED_UTF8', + 'OPERATION_ID_MISSING', + 'SOURCE_IDENTITY_MISSING', + 'UNCLASSIFIED_EVIDENCE', +]); + +function isPlainObject(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function canonicalize(value) { + if (Array.isArray(value)) return value.map(canonicalize); + if (!isPlainObject(value)) return value; + return Object.fromEntries( + Object.keys(value).sort().map((key) => [key, canonicalize(value[key])]), + ); +} + +export function canonicalJson(value) { + return JSON.stringify(canonicalize(value)); +} + +function sha256(value) { + const digest = createHash('sha256'); + digest.update(value); + return `sha256:${digest.digest('hex')}`; +} + +function sameJson(left, right) { + return canonicalJson(left) === canonicalJson(right); +} + +function sortedUnique(values) { + return [...new Set(values)].sort(); +} + +function locatorKey(value) { + return `${value.stream}:${value.line}`; +} + +function countCategories(lines) { + const counts = {}; + for (const line of lines) counts[line.kind] = (counts[line.kind] ?? 0) + 1; + return Object.fromEntries( + Object.entries(counts).sort(([left], [right]) => left.localeCompare(right)), + ); +} + +function isNonemptyStringOrNull(value) { + return value === null || (typeof value === 'string' && value.length > 0); +} + +function isSafeNonnegativeInteger(value) { + return Number.isSafeInteger(value) && value >= 0; +} + +function isSafePositiveInteger(value) { + return Number.isSafeInteger(value) && value > 0; +} + +function addViolation(violations, kind, code, path, message) { + const key = `${kind}\0${code}\0${path}\0${message}`; + if (violations.some((item) => item.key === key)) return; + violations.push({ key, kind, code, path, message }); +} + +function requireObject(value, path, violations) { + if (isPlainObject(value)) return true; + addViolation(violations, 'STRUCTURE', 'OBJECT_REQUIRED', path, 'object required'); + return false; +} + +function requireArray(value, path, violations) { + if (Array.isArray(value)) return true; + addViolation(violations, 'STRUCTURE', 'ARRAY_REQUIRED', path, 'array required'); + return false; +} + +function requireBoolean(value, path, violations) { + if (typeof value === 'boolean') return true; + addViolation(violations, 'STRUCTURE', 'BOOLEAN_REQUIRED', path, 'boolean required'); + return false; +} + +function requireHash(value, path, violations) { + if (typeof value === 'string' && SHA256_PATTERN.test(value)) return true; + addViolation( + violations, + 'STRUCTURE', + 'MALFORMED_SHA256', + path, + 'lowercase sha256:<64 hex characters> required', + ); + return false; +} + +function requireSafeCount(value, path, violations) { + if (isSafeNonnegativeInteger(value)) return true; + addViolation( + violations, + 'STRUCTURE', + 'INVALID_MEASUREMENT', + path, + 'nonnegative safe integer required', + ); + return false; +} + +function requireNullableIdentity(value, path, violations) { + if (isNonemptyStringOrNull(value)) return true; + addViolation( + violations, + 'STRUCTURE', + 'INVALID_IDENTITY', + path, + 'nonempty string or null required', + ); + return false; +} + +function requireSortedUniqueStrings(value, path, allowed, violations) { + if (!requireArray(value, path, violations)) return false; + let valid = true; + for (const [index, item] of value.entries()) { + if (typeof item !== 'string' || !allowed.has(item)) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_ENUM_VALUE', + `${path}/${index}`, + 'unsupported value', + ); + valid = false; + } + } + if (valid && !sameJson(value, sortedUnique(value))) { + addViolation( + violations, + 'CONSISTENCY', + 'NONCANONICAL_SET_ORDER', + path, + 'values must be unique and lexically sorted', + ); + } + return valid; +} + +function validateLineEvidence(value, path, allowedCategories, streamNames, violations) { + if (!requireObject(value, path, violations)) return false; + let valid = true; + if (!allowedCategories.has(value.category)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_EVIDENCE_CATEGORY', + `${path}/category`, + 'category is not valid at this location', + ); + valid = false; + } + if (!isSafePositiveInteger(value.line)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_LINE_LOCATOR', + `${path}/line`, + 'positive safe integer required', + ); + valid = false; + } + if (typeof value.stream !== 'string' || !streamNames.has(value.stream)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_STREAM_LOCATOR', + `${path}/stream`, + 'declared source stream required', + ); + valid = false; + } + if (!requireHash(value.sha256, `${path}/sha256`, violations)) valid = false; + const hasText = Object.hasOwn(value, 'text'); + const hasByteCount = Object.hasOwn(value, 'byte_count'); + if (hasText === hasByteCount) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_EVIDENCE_REPRESENTATION', + path, + 'exactly one of text or byte_count is required', + ); + valid = false; + } else if (hasText && typeof value.text !== 'string') { + addViolation(violations, 'STRUCTURE', 'STRING_REQUIRED', `${path}/text`, 'string required'); + valid = false; + } else if (hasByteCount && !isSafeNonnegativeInteger(value.byte_count)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_MEASUREMENT', + `${path}/byte_count`, + 'nonnegative safe integer required', + ); + valid = false; + } + return valid; +} + +function validateSchema(packet, violations) { + if (!requireObject(packet, '', violations)) return null; + if (packet.protocol_version !== CONTEXT_FIREWALL_PACKET_PROTOCOL) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_PACKET_VERSION', + '/protocol_version', + `only ${CONTEXT_FIREWALL_PACKET_PROTOCOL} is supported`, + ); + } + requireNullableIdentity(packet.operation_id, '/operation_id', violations); + if (!requireObject(packet.decision_evidence, '/decision_evidence', violations)) return null; + if (!requireObject(packet.receipt, '/receipt', violations)) return null; + + const evidence = packet.decision_evidence; + const receipt = packet.receipt; + if (!requireObject(receipt.source, '/receipt/source', violations)) return null; + const declaredStreams = requireArray(receipt.source.streams, '/receipt/source/streams', violations) + ? receipt.source.streams + : []; + const streamNames = new Set(); + for (const [index, stream] of declaredStreams.entries()) { + const path = `/receipt/source/streams/${index}`; + if (!requireObject(stream, path, violations)) continue; + if (!streamRank.has(stream.name) || streamNames.has(stream.name)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_SOURCE_STREAM', + `${path}/name`, + 'unique stdout or stderr stream required', + ); + } else streamNames.add(stream.name); + requireSafeCount(stream.byte_count, `${path}/byte_count`, violations); + } + if (declaredStreams.length < 1 || declaredStreams.length > 2) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_SOURCE_STREAM_COUNT', + '/receipt/source/streams', + 'one or two streams required', + ); + } + + if (!requireObject(evidence.counts, '/decision_evidence/counts', violations)) return null; + for (const field of ['passed', 'failed', 'skipped', 'total']) { + requireSafeCount(evidence.counts[field], `/decision_evidence/counts/${field}`, violations); + } + if (!dispositions.has(evidence.disposition)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_DISPOSITION', + '/decision_evidence/disposition', + 'SUFFICIENT or NEEDS_RAW_EVIDENCE required', + ); + } + if (!statuses.has(evidence.status)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_STATUS', + '/decision_evidence/status', + 'passed, failed, or indeterminate required', + ); + } + requireSortedUniqueStrings( + evidence.reason_codes, + '/decision_evidence/reason_codes', + reasonCodes, + violations, + ); + if (!requireObject(evidence.process, '/decision_evidence/process', violations)) return null; + const { duration_ms: durationMs, exit_code: exitCode } = evidence.process; + if (durationMs !== null && ( + typeof durationMs !== 'number' || !Number.isFinite(durationMs) || durationMs < 0 + )) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_DURATION', + '/decision_evidence/process/duration_ms', + 'nonnegative finite number or null required', + ); + } + if (exitCode !== null && (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > 255)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_EXIT_CODE', + '/decision_evidence/process/exit_code', + 'integer from 0 through 255 or null required', + ); + } + requireBoolean(evidence.process.interrupted, '/decision_evidence/process/interrupted', violations); + if (!requireObject(evidence.source, '/decision_evidence/source', violations)) return null; + requireNullableIdentity(evidence.source.id, '/decision_evidence/source/id', violations); + requireNullableIdentity(evidence.source.run_id, '/decision_evidence/source/run_id', violations); + + const entries = []; + const addEntry = (value, path, allowed) => { + if (validateLineEvidence(value, path, allowed, streamNames, violations)) { + entries.push({ value, path }); + } + }; + if (requireArray(evidence.failures, '/decision_evidence/failures', violations)) { + for (const [index, failure] of evidence.failures.entries()) { + const path = `/decision_evidence/failures/${index}`; + if (!requireObject(failure, path, violations)) continue; + if (typeof failure.identity !== 'string' || failure.identity.length === 0) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_FAILURE_IDENTITY', + `${path}/identity`, + 'nonempty string required', + ); + } + if (Object.hasOwn(failure, 'header')) { + addEntry(failure.header, `${path}/header`, new Set(['failed_test'])); + } + if (Object.hasOwn(failure, 'details')) { + if (requireArray(failure.details, `${path}/details`, violations)) { + for (const [detailIndex, detail] of failure.details.entries()) { + addEntry( + detail, + `${path}/details/${detailIndex}`, + new Set(['failure_message', 'assertion', 'stack_trace', 'failure_detail']), + ); + } + } + } + } + } + for (const [field, categories] of [ + ['fatal_errors', new Set(['fatal_error'])], + ['timeouts', new Set(['timeout'])], + ['warnings', new Set(['abnormal_warning'])], + ['unclassified_evidence', new Set(['unclassified', 'unclassified_binary'])], + ]) { + if (requireArray(evidence[field], `/decision_evidence/${field}`, violations)) { + for (const [index, item] of evidence[field].entries()) { + addEntry(item, `/decision_evidence/${field}/${index}`, categories); + } + } + } + + if (!requireObject(receipt.configuration, '/receipt/configuration', violations)) return null; + requireHash(receipt.configuration.identity, '/receipt/configuration/identity', violations); + if (receipt.configuration.max_output_bytes !== null + && !isSafePositiveInteger(receipt.configuration.max_output_bytes)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_PAYLOAD_CEILING', + '/receipt/configuration/max_output_bytes', + 'positive safe integer or null required', + ); + } + if (receipt.configuration.policy_revision !== CONTEXT_FIREWALL_POLICY_REVISION) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_POLICY_REVISION', + '/receipt/configuration/policy_revision', + `only ${CONTEXT_FIREWALL_POLICY_REVISION} is supported`, + ); + } + requireHash(receipt.input_hash, '/receipt/input_hash', violations); + requireHash(receipt.semantic_payload_hash, '/receipt/semantic_payload_hash', violations); + + if (!requireObject(receipt.measurements, '/receipt/measurements', violations)) return null; + for (const field of [ + 'original_bytes', + 'original_event_count', + 'reduced_bytes', + 'retained_evidence_count', + 'suppressed_evidence_count', + ]) { + requireSafeCount(receipt.measurements[field], `/receipt/measurements/${field}`, violations); + } + if (!requireObject(receipt.payload_limit, '/receipt/payload_limit', violations)) return null; + requireBoolean(receipt.payload_limit.affected, '/receipt/payload_limit/affected', violations); + if (receipt.payload_limit.honored !== true) { + addViolation( + violations, + 'STRUCTURE', + 'PAYLOAD_LIMIT_NOT_HONORED', + '/receipt/payload_limit/honored', + 'packet-v1 requires true', + ); + } + if (receipt.payload_limit.requested_bytes !== null + && !isSafePositiveInteger(receipt.payload_limit.requested_bytes)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_PAYLOAD_CEILING', + '/receipt/payload_limit/requested_bytes', + 'positive safe integer or null required', + ); + } + + if (!requireObject(receipt.raw_evidence, '/receipt/raw_evidence', violations)) return null; + const raw = receipt.raw_evidence; + if (raw.destroyed_by_reducer !== false) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_DESTRUCTION_CLAIM', + '/receipt/raw_evidence/destroyed_by_reducer', + 'this reducer contract requires false', + ); + } + requireBoolean(raw.escalation_available, '/receipt/raw_evidence/escalation_available', violations); + requireBoolean(raw.escalation_required, '/receipt/raw_evidence/escalation_required', violations); + requireNullableIdentity(raw.reference, '/receipt/raw_evidence/reference', violations); + if (!sourceEvidenceDispositions.has(raw.source_evidence_disposition)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_SOURCE_EVIDENCE_DISPOSITION', + '/receipt/raw_evidence/source_evidence_disposition', + 'unsupported disposition', + ); + } + requireBoolean( + raw.suppressed_from_model_context, + '/receipt/raw_evidence/suppressed_from_model_context', + violations, + ); + + if (receipt.receipt_version !== 1) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_RECEIPT_VERSION', + '/receipt/receipt_version', + 'only receipt version 1 is supported', + ); + } + if (!requireObject(receipt.reducer, '/receipt/reducer', violations)) return null; + if (receipt.reducer.name !== CONTEXT_FIREWALL_REDUCER) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_REDUCER', + '/receipt/reducer/name', + `only ${CONTEXT_FIREWALL_REDUCER} is supported`, + ); + } + if (receipt.reducer.version !== CONTEXT_FIREWALL_REDUCER_VERSION) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_REDUCER_VERSION', + '/receipt/reducer/version', + `only ${CONTEXT_FIREWALL_REDUCER_VERSION} is supported`, + ); + } + requireBoolean(receipt.reduction_complete, '/receipt/reduction_complete', violations); + + if (!requireObject(receipt.retained, '/receipt/retained', violations)) return null; + requireSortedUniqueStrings( + receipt.retained.categories, + '/receipt/retained/categories', + new Set([...sourceCategories, ...derivedCategories]), + violations, + ); + requireSafeCount(receipt.retained.event_count, '/receipt/retained/event_count', violations); + requireNullableIdentity(receipt.source.id, '/receipt/source/id', violations); + requireNullableIdentity(receipt.source.run_id, '/receipt/source/run_id', violations); + if (receipt.source.protocol_version !== CONTEXT_FIREWALL_INPUT_PROTOCOL) { + addViolation( + violations, + 'STRUCTURE', + 'UNSUPPORTED_SOURCE_PROTOCOL', + '/receipt/source/protocol_version', + `only ${CONTEXT_FIREWALL_INPUT_PROTOCOL} is supported`, + ); + } + + if (!requireObject(receipt.suppressed, '/receipt/suppressed', violations)) return null; + if (requireObject(receipt.suppressed.categories, '/receipt/suppressed/categories', violations)) { + for (const [category, count] of Object.entries(receipt.suppressed.categories)) { + if (!sourceCategories.has(category)) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_EVIDENCE_CATEGORY', + `/receipt/suppressed/categories/${category}`, + 'unknown source evidence category', + ); + } + requireSafeCount(count, `/receipt/suppressed/categories/${category}`, violations); + } + } + requireSafeCount(receipt.suppressed.event_count, '/receipt/suppressed/event_count', violations); + requireBoolean( + receipt.unclassified_evidence_present, + '/receipt/unclassified_evidence_present', + violations, + ); + return { evidence, receipt, entries, declaredStreams, streamNames }; +} + +function addMismatch(violations, code, path, message) { + addViolation(violations, 'CONSISTENCY', code, path, message); +} + +function addCryptoMismatch(violations, code, path, message) { + addViolation(violations, 'CRYPTOGRAPHIC', code, path, message); +} + +function deriveStatus(evidence) { + const recognizedFailure = evidence.failures.length > 0 + || evidence.fatal_errors.length > 0 + || evidence.timeouts.length > 0 + || evidence.counts.failed > 0; + const nonzero = evidence.process.exit_code !== null && evidence.process.exit_code !== 0; + if (recognizedFailure || nonzero) return 'failed'; + if (evidence.reason_codes.some((reason) => indeterminateReasonCodes.has(reason))) { + return 'indeterminate'; + } + return 'passed'; +} + +function expectedReceiptReasons(packet) { + const { decision_evidence: evidence, operation_id: operationId, receipt } = packet; + const reasons = []; + if (receipt.unclassified_evidence_present) reasons.push('UNCLASSIFIED_EVIDENCE'); + if (evidence.process.interrupted) reasons.push('INTERRUPTED_OUTPUT'); + if (evidence.process.exit_code === null) reasons.push('EXIT_STATUS_MISSING'); + const recognizedFailure = evidence.failures.length > 0 + || evidence.fatal_errors.length > 0 + || evidence.timeouts.length > 0 + || evidence.counts.failed > 0; + if (evidence.process.exit_code !== null + && evidence.process.exit_code !== 0 + && !recognizedFailure) { + reasons.push('NONZERO_EXIT_WITHOUT_RECOGNIZED_FAILURE'); + } + if (operationId === null) reasons.push('OPERATION_ID_MISSING'); + if (evidence.source.id === null) reasons.push('SOURCE_IDENTITY_MISSING'); + if (receipt.raw_evidence.reference === null) reasons.push('RAW_EVIDENCE_REFERENCE_MISSING'); + for (const reason of evidence.reason_codes) { + if (['AGGREGATE_CONTRADICTION', 'MALFORMED_UTF8'].includes(reason)) reasons.push(reason); + if (payloadReasonCodes.has(reason)) reasons.push(reason); + } + return sortedUnique(reasons); +} + +function validateConsistency(packet, schema, packetBytes, violations, claims) { + const { evidence, receipt, entries, declaredStreams } = schema; + const measurements = receipt.measurements; + const reasons = evidence.reason_codes; + const payloadReasons = reasons.filter((reason) => payloadReasonCodes.has(reason)); + + if (evidence.source.id !== receipt.source.id) { + addMismatch(violations, 'SOURCE_ID_MISMATCH', '/receipt/source/id', 'source identities differ'); + } + if (evidence.source.run_id !== receipt.source.run_id) { + addMismatch(violations, 'RUN_ID_MISMATCH', '/receipt/source/run_id', 'run identities differ'); + } + const streamOrder = declaredStreams.map((stream) => stream.name); + if (!sameJson(streamOrder, [...streamOrder].sort((left, right) => streamRank.get(left) - streamRank.get(right)))) { + addMismatch( + violations, + 'NONCANONICAL_STREAM_ORDER', + '/receipt/source/streams', + 'streams must be ordered stdout then stderr', + ); + } + const declaredOriginalBytes = declaredStreams.reduce((sum, stream) => sum + stream.byte_count, 0); + if (measurements.original_bytes !== declaredOriginalBytes) { + addMismatch( + violations, + 'ORIGINAL_BYTE_COUNT_MISMATCH', + '/receipt/measurements/original_bytes', + 'must equal the sum of source stream byte counts', + ); + } + if (measurements.retained_evidence_count !== receipt.retained.event_count) { + addMismatch( + violations, + 'RETAINED_COUNT_MISMATCH', + '/receipt/measurements/retained_evidence_count', + 'must equal receipt.retained.event_count', + ); + } + if (measurements.suppressed_evidence_count !== receipt.suppressed.event_count) { + addMismatch( + violations, + 'SUPPRESSED_COUNT_MISMATCH', + '/receipt/measurements/suppressed_evidence_count', + 'must equal receipt.suppressed.event_count', + ); + } + if (measurements.retained_evidence_count + measurements.suppressed_evidence_count + !== measurements.original_event_count) { + addMismatch( + violations, + 'EVIDENCE_ACCOUNTING_MISMATCH', + '/receipt/measurements/original_event_count', + 'retained plus suppressed must equal original event count', + ); + } + const suppressedCategoryTotal = Object.values(receipt.suppressed.categories) + .reduce((sum, count) => sum + count, 0); + if (suppressedCategoryTotal !== receipt.suppressed.event_count) { + addMismatch( + violations, + 'SUPPRESSION_CATEGORY_TOTAL_MISMATCH', + '/receipt/suppressed/categories', + 'category counts must sum to suppressed.event_count', + ); + } + + const canonicalBytes = Buffer.from(`${canonicalJson(packet)}\n`, 'utf8'); + if (measurements.reduced_bytes !== canonicalBytes.length) { + addMismatch( + violations, + 'REDUCED_BYTE_COUNT_MISMATCH', + '/receipt/measurements/reduced_bytes', + 'must equal canonical packet bytes including the final newline', + ); + } + claims.reduced_bytes = measurements.reduced_bytes === canonicalBytes.length + ? 'VERIFIED' + : 'MISMATCH'; + if (packetBytes !== undefined) { + let supplied; + try { + supplied = Buffer.isBuffer(packetBytes) ? packetBytes : Buffer.from(packetBytes); + } catch { + supplied = null; + } + if (!supplied || !supplied.equals(canonicalBytes)) { + addCryptoMismatch( + violations, + 'NONCANONICAL_PACKET_BYTES', + '/', + 'supplied packet bytes do not equal canonical packet-v1 serialization', + ); + claims.packet_serialization = 'MISMATCH'; + } else claims.packet_serialization = 'VERIFIED'; + } + + const semanticHash = sha256(Buffer.from(canonicalJson(evidence), 'utf8')); + if (semanticHash !== receipt.semantic_payload_hash) { + addCryptoMismatch( + violations, + 'SEMANTIC_PAYLOAD_HASH_MISMATCH', + '/receipt/semantic_payload_hash', + 'does not match canonical decision_evidence bytes', + ); + claims.semantic_payload_hash = 'MISMATCH'; + } else claims.semantic_payload_hash = 'VERIFIED'; + + const configurationHash = sha256(Buffer.from(canonicalJson({ + max_output_bytes: receipt.configuration.max_output_bytes, + policy_revision: receipt.configuration.policy_revision, + reducer_version: receipt.reducer.version, + }), 'utf8')); + if (configurationHash !== receipt.configuration.identity) { + addCryptoMismatch( + violations, + 'CONFIGURATION_HASH_MISMATCH', + '/receipt/configuration/identity', + 'does not match canonical packet configuration', + ); + claims.configuration_identity = 'MISMATCH'; + } else claims.configuration_identity = 'VERIFIED'; + + const locators = new Set(); + let unresolvedLineHashes = 0; + let lineHashMismatch = false; + for (const { value, path } of entries) { + const key = locatorKey(value); + if (locators.has(key)) { + addMismatch(violations, 'DUPLICATE_EVIDENCE_LOCATOR', path, 'source line is represented more than once'); + } + locators.add(key); + if (Object.hasOwn(value, 'text')) { + const digest = sha256(Buffer.from(value.text, 'utf8')); + if (digest !== value.sha256) { + addCryptoMismatch( + violations, + 'LINE_EVIDENCE_HASH_MISMATCH', + `${path}/sha256`, + 'does not match retained text bytes', + ); + lineHashMismatch = true; + } + } else unresolvedLineHashes += 1; + } + claims.line_evidence_hashes = lineHashMismatch + ? 'MISMATCH' + : unresolvedLineHashes > 0 ? 'PARTIAL' : entries.length > 0 ? 'VERIFIED' : 'NOT_APPLICABLE'; + + const retainedTextEntries = entries.filter(({ value }) => Object.hasOwn(value, 'text')); + if (receipt.retained.event_count !== retainedTextEntries.length) { + addMismatch( + violations, + 'RETAINED_LINE_COUNT_MISMATCH', + '/receipt/retained/event_count', + 'must equal the number of retained source-line texts', + ); + } + const expectedRetainedCategories = sortedUnique([ + ...derivedCategories, + ...retainedTextEntries.map(({ value }) => value.category), + ]); + if (!sameJson(receipt.retained.categories, expectedRetainedCategories)) { + addMismatch( + violations, + 'RETAINED_CATEGORY_MISMATCH', + '/receipt/retained/categories', + 'must describe the retained source-line texts plus derived evidence', + ); + } + + if (receipt.configuration.max_output_bytes !== receipt.payload_limit.requested_bytes) { + addMismatch( + violations, + 'PAYLOAD_CONFIGURATION_MISMATCH', + '/receipt/payload_limit/requested_bytes', + 'must equal configuration.max_output_bytes', + ); + } + if (receipt.payload_limit.requested_bytes !== null + && measurements.reduced_bytes > receipt.payload_limit.requested_bytes) { + addMismatch( + violations, + 'PAYLOAD_CEILING_EXCEEDED', + '/receipt/measurements/reduced_bytes', + 'canonical packet exceeds the declared honored ceiling', + ); + } + if (receipt.payload_limit.affected !== (payloadReasons.length === 1)) { + addMismatch( + violations, + 'PAYLOAD_ESCALATION_MISMATCH', + '/receipt/payload_limit/affected', + 'affected must correspond to exactly one payload reason code', + ); + } + if (payloadReasons.length > 1) { + addMismatch( + violations, + 'CONTRADICTORY_PAYLOAD_REASONS', + '/decision_evidence/reason_codes', + 'packet modes cannot declare both payload omission states', + ); + } + if (receipt.payload_limit.affected && receipt.payload_limit.requested_bytes === null) { + addMismatch( + violations, + 'MISSING_PAYLOAD_CEILING', + '/receipt/payload_limit/requested_bytes', + 'payload-affected packet requires a ceiling', + ); + } + + const expectedReasons = expectedReceiptReasons(packet); + if (!sameJson(reasons, expectedReasons)) { + addMismatch( + violations, + 'REASON_CODE_MISMATCH', + '/decision_evidence/reason_codes', + 'reason codes contradict derivable receipt state', + ); + } + const expectedDisposition = reasons.length === 0 ? 'SUFFICIENT' : 'NEEDS_RAW_EVIDENCE'; + if (evidence.disposition !== expectedDisposition) { + addMismatch( + violations, + 'DISPOSITION_MISMATCH', + '/decision_evidence/disposition', + 'SUFFICIENT is permitted only when reason_codes is empty', + ); + } + if (receipt.raw_evidence.escalation_required !== (reasons.length > 0)) { + addMismatch( + violations, + 'ESCALATION_REQUIRED_MISMATCH', + '/receipt/raw_evidence/escalation_required', + 'must match NEEDS_RAW_EVIDENCE state', + ); + } + if (receipt.reduction_complete !== (reasons.length === 0)) { + addMismatch( + violations, + 'REDUCTION_COMPLETE_MISMATCH', + '/receipt/reduction_complete', + 'complete reduction requires no escalation reasons', + ); + } + if (deriveStatus(evidence) !== evidence.status) { + addMismatch( + violations, + 'STATUS_MISMATCH', + '/decision_evidence/status', + 'status contradicts process, failures, counts, or uncertainty reasons', + ); + } + const countsAdd = evidence.counts.passed + evidence.counts.failed + evidence.counts.skipped; + if (countsAdd !== evidence.counts.total && !reasons.includes('AGGREGATE_CONTRADICTION')) { + addMismatch( + violations, + 'IMPOSSIBLE_EVIDENCE_COUNTS', + '/decision_evidence/counts/total', + 'total mismatch requires AGGREGATE_CONTRADICTION', + ); + } + if (evidence.counts.failed !== evidence.failures.length + && !reasons.includes('AGGREGATE_CONTRADICTION')) { + addMismatch( + violations, + 'FAILURE_COUNT_MISMATCH', + '/decision_evidence/counts/failed', + 'failed count must equal represented failures absent an aggregate contradiction', + ); + } + if (reasons.includes('MALFORMED_UTF8') && !evidence.unclassified_evidence + .some((item) => item.category === 'unclassified_binary')) { + addMismatch( + violations, + 'MALFORMED_UTF8_EVIDENCE_MISSING', + '/decision_evidence/unclassified_evidence', + 'MALFORMED_UTF8 requires an unclassified_binary locator', + ); + } + if (receipt.unclassified_evidence_present + !== (evidence.unclassified_evidence.length > 0)) { + addMismatch( + violations, + 'UNCLASSIFIED_FLAG_MISMATCH', + '/receipt/unclassified_evidence_present', + 'must match unclassified_evidence presence', + ); + } + + const criticalOmission = reasons.includes('PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED'); + for (const [index, failure] of evidence.failures.entries()) { + const hasRegion = Object.hasOwn(failure, 'header') && Object.hasOwn(failure, 'details'); + if (!hasRegion && !criticalOmission) { + addMismatch( + violations, + 'FAILURE_REGION_MISSING', + `/decision_evidence/failures/${index}`, + 'failure header and details may be omitted only in compact escalation mode', + ); + } + if (criticalOmission && hasRegion) { + addMismatch( + violations, + 'INVALID_COMPACT_PACKET', + `/decision_evidence/failures/${index}`, + 'compact escalation omits all critical source-line text', + ); + } + } + if (criticalOmission && ( + evidence.fatal_errors.length > 0 + || evidence.timeouts.length > 0 + || receipt.retained.event_count !== 0 + )) { + addMismatch( + violations, + 'INVALID_COMPACT_PACKET', + '/decision_evidence', + 'compact escalation cannot retain critical source-line text', + ); + } + if (!receipt.payload_limit.affected && entries.some(({ value }) => !Object.hasOwn(value, 'text'))) { + addMismatch( + violations, + 'UNDECLARED_TEXT_SUPPRESSION', + '/decision_evidence', + 'hash-only line references require a payload-affected packet', + ); + } + + const hasReference = receipt.raw_evidence.reference !== null; + if (receipt.raw_evidence.escalation_available !== hasReference) { + addMismatch( + violations, + 'RAW_ESCALATION_AVAILABILITY_MISMATCH', + '/receipt/raw_evidence/escalation_available', + 'availability means only that a caller reference was supplied', + ); + } + const expectedSourceDisposition = hasReference + ? 'CALLER_REFERENCE_SUPPLIED' + : 'PRESERVATION_UNCONFIRMED'; + if (receipt.raw_evidence.source_evidence_disposition !== expectedSourceDisposition) { + addMismatch( + violations, + 'RAW_EVIDENCE_DISPOSITION_MISMATCH', + '/receipt/raw_evidence/source_evidence_disposition', + 'disposition contradicts caller reference presence', + ); + } + if (receipt.raw_evidence.suppressed_from_model_context + !== (receipt.suppressed.event_count > 0)) { + addMismatch( + violations, + 'SUPPRESSION_STATE_MISMATCH', + '/receipt/raw_evidence/suppressed_from_model_context', + 'must match whether any source events were suppressed', + ); + } +} + +function decodeStrictBase64(value) { + if (typeof value !== 'string' + || value.length % 4 !== 0 + || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) { + throw new TypeError('invalid base64 stream data'); + } + return Buffer.from(value, 'base64'); +} + +function normalizeSuppliedStreams(options) { + if (options.sourceInput !== undefined && options.sourceStreams !== undefined) { + throw new TypeError('supply sourceInput or sourceStreams, not both'); + } + if (options.sourceInput !== undefined) { + const input = options.sourceInput; + if (!isPlainObject(input) || !Array.isArray(input.streams)) { + throw new TypeError('sourceInput with streams is required'); + } + const streams = input.streams.map((stream) => { + if (!isPlainObject(stream) || !streamRank.has(stream.name) || typeof stream.data !== 'string') { + throw new TypeError('sourceInput stream must contain stdout/stderr string data'); + } + const encoding = stream.encoding ?? 'utf8'; + if (!['utf8', 'base64'].includes(encoding)) throw new TypeError('unsupported stream encoding'); + return { + name: stream.name, + bytes: encoding === 'base64' ? decodeStrictBase64(stream.data) : Buffer.from(stream.data, 'utf8'), + }; + }); + return { streams: normalizeStreamList(streams), sourceInput: input }; + } + if (options.sourceStreams !== undefined) { + if (!Array.isArray(options.sourceStreams)) throw new TypeError('sourceStreams must be an array'); + const streams = options.sourceStreams.map((stream) => { + if (!isPlainObject(stream) || !streamRank.has(stream.name)) { + throw new TypeError('source stream name must be stdout or stderr'); + } + let bytes; + if (typeof stream.bytes === 'string') bytes = Buffer.from(stream.bytes, 'utf8'); + else if (Buffer.isBuffer(stream.bytes) || stream.bytes instanceof Uint8Array) { + bytes = Buffer.from(stream.bytes); + } else throw new TypeError('source stream bytes required'); + return { name: stream.name, bytes }; + }); + return { streams: normalizeStreamList(streams), sourceInput: null }; + } + return null; +} + +function normalizeStreamList(streams) { + if (streams.length < 1 || streams.length > 2) throw new TypeError('one or two source streams required'); + const names = new Set(); + for (const stream of streams) { + if (names.has(stream.name)) throw new TypeError(`duplicate ${stream.name} stream`); + names.add(stream.name); + } + return [...streams].sort((left, right) => streamRank.get(left.name) - streamRank.get(right.name)); +} + +function streamDigest(streams) { + const digest = createHash('sha256'); + for (const stream of streams) { + digest.update(Buffer.from(`${stream.name}\0${stream.bytes.length}\0`, 'utf8')); + digest.update(stream.bytes); + digest.update(Buffer.from('\0', 'utf8')); + } + return `sha256:${digest.digest('hex')}`; +} + +function splitLines(text) { + if (text.length === 0) return []; + const lines = text.split('\n'); + if (text.endsWith('\n')) lines.pop(); + return lines; +} + +function classifyFailureDetail(clean) { + if (/^\s*(?:operator|expected|actual|diff|code):/i.test(clean)) return 'assertion'; + if (/^\s*(?:[A-Za-z]*Error:|at\s+|.*\([^()]+:\d+:\d+\)\s*$)/.test(clean)) return 'stack_trace'; + if (/^\s*(?:error|message|cause):/i.test(clean)) return 'failure_message'; + return 'failure_detail'; +} + +function independentlyClassify(streams) { + const lines = []; + const failures = []; + const fatalErrors = []; + const timeouts = []; + const warnings = []; + const unclassified = []; + const summaryValues = new Map(); + const summaryConflicts = []; + const observed = { passed: 0, failed: 0, skipped: 0 }; + let malformedUtf8 = false; + + for (const stream of streams) { + let text; + try { + text = utf8Decoder.decode(stream.bytes); + } catch { + malformedUtf8 = true; + if (stream.bytes.length > 0) { + const line = { + stream: stream.name, + line: 1, + text: `[non-UTF-8 ${stream.bytes.length} bytes; ${sha256(stream.bytes)}]`, + kind: 'unclassified_binary', + }; + lines.push(line); + unclassified.push(line); + } + continue; + } + let currentFailure = null; + for (const [index, rawLine] of splitLines(text).entries()) { + const line = { stream: stream.name, line: index + 1, text: rawLine }; + const clean = rawLine.replace(/\r$/, '').replace(ANSI_PATTERN, ''); + const marker = clean.match(/^\s*(not ok|ok)\b(?:\s+\d+)?(?:\s*-\s*)?(.*)$/); + const summary = clean.match(/^\s*#\s*(tests|pass|fail|skipped)\s+(\d+)\s*$/); + if (marker) { + currentFailure = null; + const directive = marker[2].match(/\s+#\s*(SKIP|TODO)\b.*$/i); + const identity = marker[2].replace(/\s+#\s*(?:SKIP|TODO)\b.*$/i, '').trim() + || '(unnamed test)'; + if (marker[1] === 'ok' && directive?.[1].toUpperCase() === 'SKIP') { + line.kind = 'skipped_test'; + observed.skipped += 1; + } else if (marker[1] === 'ok') { + line.kind = 'successful_test'; + observed.passed += 1; + } else if (directive?.[1].toUpperCase() === 'TODO') { + line.kind = 'unclassified'; + unclassified.push(line); + } else { + line.kind = 'failed_test'; + observed.failed += 1; + currentFailure = { identity, header: line, details: [] }; + failures.push(currentFailure); + } + } else if (summary) { + currentFailure = null; + line.kind = 'aggregate_source'; + const key = summary[1] === 'pass' ? 'passed' + : summary[1] === 'fail' ? 'failed' : summary[1]; + const value = Number(summary[2]); + if (summaryValues.has(key) && summaryValues.get(key) !== value) summaryConflicts.push(key); + summaryValues.set(key, value); + } else if (currentFailure && !/^\s*(?:TAP version \d+|1\.\.\d+|#\s*Subtest:.*)\s*$/.test(clean)) { + line.kind = classifyFailureDetail(clean); + currentFailure.details.push(line); + } else if (/^\s*(?:TAP version \d+|1\.\.\d+|#\s*Subtest:.*|---|\.\.\.)\s*$/.test(clean)) { + currentFailure = null; + line.kind = 'structure'; + } else { + const fatal = clean.match(/^\s*(?:#\s*)?(?:FATAL|RUNNER CRASH|UNCAUGHT):\s*(.+)$/); + const timeout = clean.match(/^\s*(?:#\s*)?TIMEOUT:\s*(.+)$/); + const warning = clean.match(/^\s*(?:#\s*)?(?:WARNING|WARN):\s*(.+)$/); + if (fatal) { + line.kind = 'fatal_error'; + fatalErrors.push(line); + } else if (timeout) { + line.kind = 'timeout'; + timeouts.push(line); + } else if (warning) { + line.kind = 'abnormal_warning'; + warnings.push(line); + } else if (/^\s*#\s*(?:duration_ms\s+\d+(?:\.\d+)?|note:\s*.*)\s*$/.test(clean)) { + line.kind = clean.includes('duration_ms') ? 'duration_source' : 'informational'; + } else if (/^\s*$/.test(clean)) line.kind = 'blank'; + else { + line.kind = 'unclassified'; + unclassified.push(line); + } + } + lines.push(line); + } + } + const counts = { + passed: summaryValues.get('passed') ?? observed.passed, + failed: summaryValues.get('failed') ?? observed.failed, + skipped: summaryValues.get('skipped') ?? observed.skipped, + total: summaryValues.get('tests') + ?? ((summaryValues.has('passed') || summaryValues.has('failed') || summaryValues.has('skipped')) + ? (summaryValues.get('passed') ?? 0) + (summaryValues.get('failed') ?? 0) + + (summaryValues.get('skipped') ?? 0) + : observed.passed + observed.failed + observed.skipped), + }; + const contradictions = [...summaryConflicts]; + for (const key of ['passed', 'failed', 'skipped']) { + if (summaryValues.has(key) && summaryValues.get(key) !== observed[key]) contradictions.push(key); + } + if (summaryValues.has('tests') + && summaryValues.get('tests') !== observed.passed + observed.failed + observed.skipped) { + contradictions.push('total'); + } + return { + lines, + failures, + fatalErrors, + timeouts, + warnings, + unclassified, + counts, + contradictions: sortedUnique(contradictions), + malformedUtf8, + }; +} + +function expectedSourceReasons(packet, parsed) { + const reasons = []; + const evidence = packet.decision_evidence; + const recognizedFailure = parsed.failures.length > 0 + || parsed.fatalErrors.length > 0 + || parsed.timeouts.length > 0 + || parsed.counts.failed > 0; + const nonzero = evidence.process.exit_code !== null && evidence.process.exit_code !== 0; + if (parsed.unclassified.length > 0) reasons.push('UNCLASSIFIED_EVIDENCE'); + if (parsed.malformedUtf8) reasons.push('MALFORMED_UTF8'); + if (parsed.contradictions.length > 0) reasons.push('AGGREGATE_CONTRADICTION'); + if (evidence.process.interrupted) reasons.push('INTERRUPTED_OUTPUT'); + if (evidence.process.exit_code === null) reasons.push('EXIT_STATUS_MISSING'); + if (nonzero && !recognizedFailure) reasons.push('NONZERO_EXIT_WITHOUT_RECOGNIZED_FAILURE'); + if (packet.operation_id === null) reasons.push('OPERATION_ID_MISSING'); + if (evidence.source.id === null) reasons.push('SOURCE_IDENTITY_MISSING'); + if (packet.receipt.raw_evidence.reference === null) reasons.push('RAW_EVIDENCE_REFERENCE_MISSING'); + for (const reason of evidence.reason_codes) { + if (payloadReasonCodes.has(reason)) reasons.push(reason); + } + return sortedUnique(reasons); +} + +function compareLocatorList(actual, expected, path, violations) { + const actualLocators = actual.map((item) => locatorKey(item)); + const expectedLocators = expected.map((item) => locatorKey(item)); + if (!sameJson(actualLocators, expectedLocators)) { + addMismatch( + violations, + 'SOURCE_EVIDENCE_LOCATOR_MISMATCH', + path, + 'source evidence locators do not match independent classification', + ); + } +} + +function verifySource(packet, schema, supplied, violations, claims) { + const { streams, sourceInput } = supplied; + const { evidence, receipt, entries } = schema; + const digest = streamDigest(streams); + if (digest !== receipt.input_hash) { + addCryptoMismatch( + violations, + 'INPUT_HASH_MISMATCH', + '/receipt/input_hash', + 'does not match independently framed source bytes', + ); + claims.input_hash = 'MISMATCH'; + } else claims.input_hash = 'VERIFIED'; + + const expectedStreamReceipt = streams.map((stream) => ({ + byte_count: stream.bytes.length, + name: stream.name, + })); + if (!sameJson(receipt.source.streams, expectedStreamReceipt)) { + addMismatch( + violations, + 'SOURCE_STREAM_RECEIPT_MISMATCH', + '/receipt/source/streams', + 'stream names or byte counts do not match supplied source bytes', + ); + } + if (sourceInput) { + const source = isPlainObject(sourceInput.source) ? sourceInput.source : {}; + const process = isPlainObject(sourceInput.process) ? sourceInput.process : {}; + const expectedMetadata = { + operation_id: typeof sourceInput.operation_id === 'string' && sourceInput.operation_id.length > 0 + ? sourceInput.operation_id : null, + protocol_version: sourceInput.protocol_version, + source_id: typeof source.id === 'string' && source.id.length > 0 ? source.id : null, + run_id: typeof source.run_id === 'string' && source.run_id.length > 0 ? source.run_id : null, + raw_evidence_ref: typeof source.raw_evidence_ref === 'string' && source.raw_evidence_ref.length > 0 + ? source.raw_evidence_ref : null, + exit_code: process.exit_code ?? null, + duration_ms: process.duration_ms ?? null, + interrupted: process.interrupted ?? false, + }; + const packetMetadata = { + operation_id: packet.operation_id, + protocol_version: receipt.source.protocol_version, + source_id: receipt.source.id, + run_id: receipt.source.run_id, + raw_evidence_ref: receipt.raw_evidence.reference, + exit_code: evidence.process.exit_code, + duration_ms: evidence.process.duration_ms, + interrupted: evidence.process.interrupted, + }; + if (!sameJson(expectedMetadata, packetMetadata)) { + addMismatch( + violations, + 'SOURCE_METADATA_MISMATCH', + '/receipt/source', + 'packet provenance or process state does not match supplied source input', + ); + } + } + + const parsed = independentlyClassify(streams); + const lineByLocator = new Map(parsed.lines.map((line) => [locatorKey(line), line])); + let sourceLineMismatch = false; + for (const { value, path } of entries) { + const sourceLine = lineByLocator.get(locatorKey(value)); + if (!sourceLine + || sourceLine.kind !== value.category + || sha256(Buffer.from(sourceLine.text, 'utf8')) !== value.sha256 + || (Object.hasOwn(value, 'text') && sourceLine.text !== value.text) + || (Object.hasOwn(value, 'byte_count') + && Buffer.byteLength(sourceLine.text, 'utf8') !== value.byte_count)) { + addCryptoMismatch( + violations, + 'SOURCE_LINE_MISMATCH', + path, + 'line locator, category, bytes, or digest does not match supplied source', + ); + sourceLineMismatch = true; + } + } + if (!sourceLineMismatch && entries.length > 0) claims.line_evidence_hashes = 'VERIFIED'; + + if (!sameJson(evidence.counts, parsed.counts)) { + addMismatch( + violations, + 'SOURCE_AGGREGATE_MISMATCH', + '/decision_evidence/counts', + 'counts do not match independent source classification', + ); + } + const expectedReasons = expectedSourceReasons(packet, parsed); + if (!sameJson(evidence.reason_codes, expectedReasons)) { + addMismatch( + violations, + 'SOURCE_REASON_MISMATCH', + '/decision_evidence/reason_codes', + 'reason codes do not match independent source classification', + ); + } + if (receipt.unclassified_evidence_present !== (parsed.unclassified.length > 0)) { + addMismatch( + violations, + 'SOURCE_UNCLASSIFIED_MISMATCH', + '/receipt/unclassified_evidence_present', + 'unclassified flag does not match supplied source', + ); + } + if (receipt.measurements.original_event_count !== parsed.lines.length) { + addMismatch( + violations, + 'SOURCE_EVENT_COUNT_MISMATCH', + '/receipt/measurements/original_event_count', + 'does not match independently parsed source events', + ); + } + + if (evidence.failures.length !== parsed.failures.length + || evidence.failures.some((failure, index) => failure.identity !== parsed.failures[index]?.identity)) { + addMismatch( + violations, + 'SOURCE_FAILURE_IDENTITY_MISMATCH', + '/decision_evidence/failures', + 'failure identities do not match supplied source', + ); + } + const compact = evidence.reason_codes.includes('PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED'); + if (!compact) { + compareLocatorList( + evidence.failures.flatMap((failure) => failure.header ? [failure.header, ...failure.details] : []), + parsed.failures.flatMap((failure) => [failure.header, ...failure.details]), + '/decision_evidence/failures', + violations, + ); + compareLocatorList(evidence.fatal_errors, parsed.fatalErrors, '/decision_evidence/fatal_errors', violations); + compareLocatorList(evidence.timeouts, parsed.timeouts, '/decision_evidence/timeouts', violations); + } + compareLocatorList(evidence.warnings, parsed.warnings, '/decision_evidence/warnings', violations); + compareLocatorList( + evidence.unclassified_evidence, + parsed.unclassified, + '/decision_evidence/unclassified_evidence', + violations, + ); + + const retainedLocators = new Set(entries + .filter(({ value }) => Object.hasOwn(value, 'text')) + .map(({ value }) => locatorKey(value))); + const retainedLines = parsed.lines.filter((line) => retainedLocators.has(locatorKey(line))); + const suppressedLines = parsed.lines.filter((line) => !retainedLocators.has(locatorKey(line))); + if (receipt.retained.event_count !== retainedLines.length + || receipt.suppressed.event_count !== suppressedLines.length + || !sameJson(receipt.suppressed.categories, countCategories(suppressedLines))) { + addMismatch( + violations, + 'SOURCE_SUPPRESSION_ACCOUNTING_MISMATCH', + '/receipt/suppressed', + 'retained/suppressed accounting does not match independent source classification', + ); + } + claims.source_accounting = violations.some((item) => [ + 'SOURCE_AGGREGATE_MISMATCH', + 'SOURCE_REASON_MISMATCH', + 'SOURCE_UNCLASSIFIED_MISMATCH', + 'SOURCE_EVENT_COUNT_MISMATCH', + 'SOURCE_FAILURE_IDENTITY_MISMATCH', + 'SOURCE_EVIDENCE_LOCATOR_MISMATCH', + 'SOURCE_SUPPRESSION_ACCOUNTING_MISMATCH', + ].includes(item.code)) ? 'MISMATCH' : 'VERIFIED'; +} + +function resultClassification(violations, sourceSupplied) { + if (violations.some((item) => item.kind === 'STRUCTURE')) return 'STRUCTURALLY_INVALID'; + if (violations.some((item) => item.kind === 'CRYPTOGRAPHIC')) return 'CRYPTOGRAPHIC_MISMATCH'; + if (violations.some((item) => item.kind === 'CONSISTENCY')) return 'INTERNALLY_INCONSISTENT'; + return sourceSupplied ? 'VALID' : 'VALID_WITH_UNVERIFIED_SOURCE'; +} + +/** + * Validate a Context Firewall packet-v1 object independently. + * + * `packetBytes` verifies the exact canonical output serialization. + * `sourceInput` accepts the producer's public input envelope and verifies both + * metadata and exact stream bytes. `sourceStreams` accepts only raw streams as + * `{ name, bytes }` entries. Supply at most one source form. + */ +export function validateContextFirewallPacket(packet, options = {}) { + const violations = []; + const claims = { + configuration_identity: 'NOT_EVALUATED', + input_hash: 'NOT_SUPPLIED', + line_evidence_hashes: 'NOT_EVALUATED', + packet_serialization: options.packetBytes === undefined ? 'NOT_SUPPLIED' : 'NOT_EVALUATED', + reduced_bytes: 'NOT_EVALUATED', + semantic_payload_hash: 'NOT_EVALUATED', + source_accounting: 'NOT_SUPPLIED', + }; + const schema = validateSchema(packet, violations); + let supplied = null; + try { + supplied = normalizeSuppliedStreams(options); + } catch (error) { + addViolation( + violations, + 'STRUCTURE', + 'INVALID_SOURCE_MATERIAL', + '/source_material', + error.message, + ); + } + + if (schema && !violations.some((item) => item.kind === 'STRUCTURE')) { + validateConsistency(packet, schema, options.packetBytes, violations, claims); + if (supplied) verifySource(packet, schema, supplied, violations, claims); + } + + violations.sort((left, right) => ( + left.kind.localeCompare(right.kind) + || left.path.localeCompare(right.path) + || left.code.localeCompare(right.code) + || left.message.localeCompare(right.message) + )); + const publicViolations = violations.map(({ key: _key, ...item }) => item); + const classification = resultClassification(violations, Boolean(supplied)); + const valid = publicViolations.length === 0; + const declared = schema?.evidence?.disposition ?? 'UNKNOWN'; + const sufficiency = !valid ? 'INVALID' + : declared === 'NEEDS_RAW_EVIDENCE' ? 'NEEDS_RAW_EVIDENCE' : 'SUFFICIENT'; + const cryptographicVerification = violations.some((item) => item.kind === 'CRYPTOGRAPHIC') + ? 'MISMATCH' + : claims.input_hash === 'VERIFIED' && claims.packet_serialization === 'VERIFIED' + ? 'VERIFIED' : 'PARTIAL'; + const rawReceipt = schema?.receipt?.raw_evidence; + return { + classification, + cryptographic_verification: cryptographicVerification, + evidence_sufficient: sufficiency === 'SUFFICIENT', + protocol_version: packet?.protocol_version ?? null, + raw_evidence: rawReceipt ? { + destroyed_by_reducer: rawReceipt.destroyed_by_reducer, + locator_verification: rawReceipt.reference === null + ? 'NOT_SUPPLIED' : 'CALLER_CLAIM_ONLY', + source_evidence_disposition: rawReceipt.source_evidence_disposition, + suppressed_from_model_context: rawReceipt.suppressed_from_model_context, + } : null, + raw_evidence_required: sufficiency === 'NEEDS_RAW_EVIDENCE', + structural_valid: !violations.some((item) => item.kind === 'STRUCTURE'), + internally_consistent: publicViolations.length === 0, + sufficiency, + valid, + verification: claims, + violations: publicViolations, + }; +} diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..a1abb3d --- /dev/null +++ b/src/index.js @@ -0,0 +1,10 @@ +export { validateEnvelope } from './validate.js'; +export { + CONTEXT_FIREWALL_INPUT_PROTOCOL, + CONTEXT_FIREWALL_PACKET_PROTOCOL, + CONTEXT_FIREWALL_POLICY_REVISION, + CONTEXT_FIREWALL_REDUCER, + CONTEXT_FIREWALL_REDUCER_VERSION, + canonicalJson, + validateContextFirewallPacket, +} from './context-firewall-v1.js'; diff --git a/tests/README.md b/tests/README.md index 1795ee7..06996f8 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,3 +1,9 @@ # Test strategy -Tests must map to named invariants in SPEC.md, include adversarial failure cases, and separate implementation correctness from evidence for the broader hypothesis. +Tests map to named invariants in `SPEC.md` and the Context Firewall packet-v1 +profile. They include adversarial structural, internal-consistency, +cryptographic, provenance, sufficiency, deterministic-output, malformed-input, +and numeric boundary cases. + +Passing tests establish validator behavior at one exact revision. They do not +establish model correctness or comparative context-reduction benefit. diff --git a/tests/context-firewall-v1.test.js b/tests/context-firewall-v1.test.js new file mode 100644 index 0000000..d96e267 --- /dev/null +++ b/tests/context-firewall-v1.test.js @@ -0,0 +1,321 @@ +import assert from 'node:assert/strict'; +import { createHash } from 'node:crypto'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; +import { fileURLToPath } from 'node:url'; + +import { + canonicalJson, + validateContextFirewallPacket, +} from '../src/context-firewall-v1.js'; +import { + loadContextFirewallVectors, + runContextFirewallConformance, +} from '../src/conformance.js'; + +const cliPath = fileURLToPath(new URL('../bin/decision-evidence.js', import.meta.url)); +const vectors = await loadContextFirewallVectors(); + +function vector(id) { + const result = vectors.valid_vectors.find((item) => item.id === id); + assert.ok(result, `missing vector ${id}`); + return structuredClone(result); +} + +function packetBytes(packet) { + return Buffer.from(`${canonicalJson(packet)}\n`, 'utf8'); +} + +function refreshReducedBytes(packet) { + let length = packet.receipt.measurements.reduced_bytes; + for (let iteration = 0; iteration < 12; iteration += 1) { + packet.receipt.measurements.reduced_bytes = length; + const next = packetBytes(packet).length; + if (next === length) return; + length = next; + } + throw new Error('test packet measurement did not converge'); +} + +function sha256Canonical(value) { + const digest = createHash('sha256'); + digest.update(Buffer.from(canonicalJson(value), 'utf8')); + return `sha256:${digest.digest('hex')}`; +} + +test('validates a receipt alone without claiming source verification', () => { + const { packet } = vector('successful-reduced-run'); + const result = validateContextFirewallPacket(packet); + assert.equal(result.valid, true); + assert.equal(result.classification, 'VALID_WITH_UNVERIFIED_SOURCE'); + assert.equal(result.sufficiency, 'SUFFICIENT'); + assert.equal(result.evidence_sufficient, true); + assert.equal(result.verification.input_hash, 'NOT_SUPPLIED'); + assert.equal(result.cryptographic_verification, 'PARTIAL'); + assert.equal(result.raw_evidence.locator_verification, 'CALLER_CLAIM_ONLY'); +}); + +test('rejects unsupported packet, receipt, source, reducer, and policy versions', async (context) => { + const cases = [ + ['/protocol_version', 'unsupported', 'UNSUPPORTED_PACKET_VERSION'], + ['/receipt/receipt_version', 2, 'UNSUPPORTED_RECEIPT_VERSION'], + ['/receipt/source/protocol_version', 'unsupported', 'UNSUPPORTED_SOURCE_PROTOCOL'], + ['/receipt/reducer/version', '9.0.0', 'UNSUPPORTED_REDUCER_VERSION'], + ['/receipt/configuration/policy_revision', 'policy/v9', 'UNSUPPORTED_POLICY_REVISION'], + ]; + for (const [path, value, code] of cases) { + await context.test(code, () => { + const item = vector('successful-reduced-run'); + const parts = path.slice(1).split('/'); + const final = parts.pop(); + let parent = item.packet; + for (const part of parts) parent = parent[part]; + parent[final] = value; + const result = validateContextFirewallPacket(item.packet); + assert.equal(result.classification, 'STRUCTURALLY_INVALID'); + assert.ok(result.violations.some((violation) => violation.code === code)); + }); + } +}); + +test('independently verifies source bytes, source accounting, and canonical packet bytes', () => { + const item = vector('failed-test-packet'); + const result = validateContextFirewallPacket(item.packet, { + packetBytes: packetBytes(item.packet), + sourceInput: item.source_input, + }); + assert.equal(result.valid, true); + assert.equal(result.classification, 'VALID'); + assert.equal(result.cryptographic_verification, 'VERIFIED'); + assert.deepEqual(result.verification, { + configuration_identity: 'VERIFIED', + input_hash: 'VERIFIED', + line_evidence_hashes: 'VERIFIED', + packet_serialization: 'VERIFIED', + reduced_bytes: 'VERIFIED', + semantic_payload_hash: 'VERIFIED', + source_accounting: 'VERIFIED', + }); +}); + +test('NEEDS_RAW_EVIDENCE is valid but never sufficient', () => { + const item = vector('valid-needs-raw-evidence'); + const result = validateContextFirewallPacket(item.packet, { + packetBytes: packetBytes(item.packet), + sourceInput: item.source_input, + }); + assert.equal(result.valid, true); + assert.equal(result.sufficiency, 'NEEDS_RAW_EVIDENCE'); + assert.equal(result.raw_evidence_required, true); + assert.equal(result.evidence_sufficient, false); +}); + +test('ambiguous evidence is accepted only as an escalation state', () => { + const item = vector('ambiguous-evidence-escalation'); + const result = validateContextFirewallPacket(item.packet, { sourceInput: item.source_input }); + assert.equal(result.valid, true); + assert.equal(result.raw_evidence_required, true); + assert.ok(item.packet.decision_evidence.reason_codes.includes('UNCLASSIFIED_EVIDENCE')); +}); + +test('ceiling-triggered critical omission is valid only as insufficient evidence', () => { + const item = vector('ceiling-triggered-escalation'); + const result = validateContextFirewallPacket(item.packet, { sourceInput: item.source_input }); + assert.equal(result.valid, true); + assert.equal(result.sufficiency, 'NEEDS_RAW_EVIDENCE'); + assert.equal(item.packet.receipt.retained.event_count, 0); + assert.ok(item.packet.decision_evidence.reason_codes + .includes('PAYLOAD_LIMIT_CRITICAL_EVIDENCE_EXCEEDED')); +}); + +test('large successful suppression is independently accounted for', () => { + const item = vector('large-successful-suppression'); + const result = validateContextFirewallPacket(item.packet, { sourceInput: item.source_input }); + assert.equal(result.valid, true); + assert.equal(result.verification.source_accounting, 'VERIFIED'); + assert.equal(item.packet.receipt.suppressed.categories.successful_test, 1500); + assert.equal( + item.packet.receipt.measurements.retained_evidence_count + + item.packet.receipt.measurements.suppressed_evidence_count, + item.packet.receipt.measurements.original_event_count, + ); +}); + +test('exact payload boundary remains valid byte for byte', () => { + const item = vector('exact-payload-boundary'); + const result = validateContextFirewallPacket(item.packet, { + packetBytes: packetBytes(item.packet), + sourceInput: item.source_input, + }); + assert.equal(result.valid, true); + assert.equal( + item.packet.receipt.measurements.reduced_bytes, + item.packet.receipt.payload_limit.requested_bytes, + ); +}); + +test('source byte tampering is a cryptographic mismatch', () => { + const item = vector('successful-reduced-run'); + item.source_input.streams[0].data += 'WARNING: tampered\n'; + const result = validateContextFirewallPacket(item.packet, { sourceInput: item.source_input }); + assert.equal(result.classification, 'CRYPTOGRAPHIC_MISMATCH'); + assert.ok(result.violations.some((violation) => violation.code === 'INPUT_HASH_MISMATCH')); +}); + +test('semantic payload tampering is independently detected', () => { + const item = vector('successful-reduced-run'); + item.packet.decision_evidence.counts.passed = 3; + refreshReducedBytes(item.packet); + const result = validateContextFirewallPacket(item.packet); + assert.equal(result.classification, 'CRYPTOGRAPHIC_MISMATCH'); + assert.ok(result.violations + .some((violation) => violation.code === 'SEMANTIC_PAYLOAD_HASH_MISMATCH')); +}); + +test('configuration identity is recomputed independently', () => { + const item = vector('successful-reduced-run'); + item.packet.receipt.configuration.identity = `sha256:${'f'.repeat(64)}`; + refreshReducedBytes(item.packet); + const result = validateContextFirewallPacket(item.packet); + assert.equal(result.classification, 'CRYPTOGRAPHIC_MISMATCH'); + assert.ok(result.violations.some((violation) => violation.code === 'CONFIGURATION_HASH_MISMATCH')); +}); + +test('noncanonical supplied packet bytes are rejected', () => { + const item = vector('successful-reduced-run'); + const bytes = Buffer.concat([packetBytes(item.packet), Buffer.from(' ')]); + const result = validateContextFirewallPacket(item.packet, { packetBytes: bytes }); + assert.equal(result.classification, 'CRYPTOGRAPHIC_MISMATCH'); + assert.ok(result.violations.some((violation) => violation.code === 'NONCANONICAL_PACKET_BYTES')); +}); + +test('source metadata mismatch is distinct from byte-hash verification', () => { + const item = vector('successful-reduced-run'); + item.source_input.source.id = 'different-source'; + const result = validateContextFirewallPacket(item.packet, { sourceInput: item.source_input }); + assert.equal(result.classification, 'INTERNALLY_INCONSISTENT'); + assert.equal(result.verification.input_hash, 'VERIFIED'); + assert.ok(result.violations.some((violation) => violation.code === 'SOURCE_METADATA_MISMATCH')); +}); + +test('sourceStreams verifies bytes without claiming caller metadata', () => { + const item = vector('successful-reduced-run'); + const streams = item.source_input.streams.map((stream) => ({ + bytes: Buffer.from(stream.data, 'utf8'), + name: stream.name, + })); + const result = validateContextFirewallPacket(item.packet, { sourceStreams: streams }); + assert.equal(result.valid, true); + assert.equal(result.verification.input_hash, 'VERIFIED'); + assert.equal(result.verification.source_accounting, 'VERIFIED'); +}); + +test('invalid supplied source material fails structurally', () => { + const { packet } = vector('successful-reduced-run'); + const result = validateContextFirewallPacket(packet, { + sourceStreams: [ + { name: 'stdout', bytes: 'one' }, + { name: 'stdout', bytes: 'two' }, + ], + }); + assert.equal(result.classification, 'STRUCTURALLY_INVALID'); + assert.ok(result.violations.some((violation) => violation.code === 'INVALID_SOURCE_MATERIAL')); +}); + +test('caller-owned locator syntax is deliberately limited to nonempty strings', () => { + const item = vector('successful-reduced-run'); + item.packet.receipt.raw_evidence.reference = 'opaque-caller-token'; + refreshReducedBytes(item.packet); + const result = validateContextFirewallPacket(item.packet); + assert.equal(result.valid, true); + assert.equal(result.classification, 'VALID_WITH_UNVERIFIED_SOURCE'); +}); + +test('raw reference absence is preservation-unconfirmed, not destruction', () => { + const item = vectors.valid_vectors.find((entry) => entry.producer_fixture === 'provenance/raw-reference-present'); + const packet = structuredClone(item.packet); + packet.receipt.raw_evidence.reference = null; + packet.receipt.raw_evidence.escalation_available = false; + packet.receipt.raw_evidence.escalation_required = true; + packet.receipt.raw_evidence.source_evidence_disposition = 'PRESERVATION_UNCONFIRMED'; + packet.receipt.reduction_complete = false; + packet.decision_evidence.disposition = 'NEEDS_RAW_EVIDENCE'; + packet.decision_evidence.reason_codes = ['RAW_EVIDENCE_REFERENCE_MISSING']; + packet.receipt.semantic_payload_hash = sha256Canonical(packet.decision_evidence); + refreshReducedBytes(packet); + const result = validateContextFirewallPacket(packet); + assert.equal(result.valid, true); + assert.equal(result.raw_evidence_required, true); + assert.equal(packet.receipt.raw_evidence.destroyed_by_reducer, false); +}); + +test('malformed top-level input produces specific structural violations', () => { + const result = validateContextFirewallPacket(null); + assert.equal(result.valid, false); + assert.equal(result.structural_valid, false); + assert.equal(result.internally_consistent, false); + assert.deepEqual(result.violations.map((violation) => violation.code), ['OBJECT_REQUIRED']); +}); + +test('validator output is deterministic', () => { + const item = vector('failed-test-packet'); + const options = { packetBytes: packetBytes(item.packet), sourceInput: item.source_input }; + assert.deepEqual( + validateContextFirewallPacket(item.packet, options), + validateContextFirewallPacket(item.packet, options), + ); +}); + +test('all protocol-owned vectors pass their expected result', async () => { + const report = await runContextFirewallConformance(); + assert.equal(report.conformance, 'PASS'); + assert.equal(report.vector_count, 24); + assert.equal(report.valid_vector_count, 9); + assert.equal(report.invalid_vector_count, 15); + assert.equal(report.results.every((result) => result.pass), true); +}); + +test('every invalid vector proves a specific failure code', async (context) => { + const report = await runContextFirewallConformance(); + for (const result of report.results.filter((item) => item.kind === 'invalid')) { + await context.test(result.id, () => { + assert.equal(result.pass, true); + assert.equal(result.actual.valid, false); + assert.ok(result.actual.violation_codes.length > 0); + }); + } +}); + +test('CLI emits machine-readable receipt-only validation', () => { + const item = vector('successful-reduced-run'); + const result = spawnSync(process.execPath, [cliPath, 'validate-context-firewall'], { + encoding: 'utf8', + input: packetBytes(item.packet), + }); + assert.equal(result.status, 0, result.stderr); + const output = JSON.parse(result.stdout); + assert.equal(output.classification, 'VALID_WITH_UNVERIFIED_SOURCE'); + assert.equal(output.sufficiency, 'SUFFICIENT'); +}); + +test('CLI rejects malformed JSON with machine-readable control output', () => { + const result = spawnSync(process.execPath, [cliPath, 'validate-context-firewall'], { + encoding: 'utf8', + input: '{broken', + }); + assert.equal(result.status, 2); + assert.equal(result.stdout, ''); + assert.deepEqual(JSON.parse(result.stderr), { + code: 'INVALID_INVOCATION', + message: 'packet must be valid JSON', + }); +}); + +test('CLI conformance output is deterministic', () => { + const first = spawnSync(process.execPath, [cliPath, 'conformance'], { encoding: 'utf8' }); + const second = spawnSync(process.execPath, [cliPath, 'conformance'], { encoding: 'utf8' }); + assert.equal(first.status, 0, first.stderr); + assert.equal(second.status, 0, second.stderr); + assert.equal(first.stdout, second.stdout); + assert.equal(JSON.parse(first.stdout).conformance, 'PASS'); +}); diff --git a/tools/generate-context-firewall-vectors.js b/tools/generate-context-firewall-vectors.js new file mode 100755 index 0000000..38e08e0 --- /dev/null +++ b/tools/generate-context-firewall-vectors.js @@ -0,0 +1,178 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import process from 'node:process'; +import { pathToFileURL } from 'node:url'; + +const EXPECTED_CONTEXT_FIREWALL_SHA = 'dd34bd9f681314761f1ca87f339648bf611811f3'; + +function parseArgs(args) { + const options = { + contextFirewall: '../context-firewall', + output: 'fixtures/context-firewall-packet-v1/vectors.json', + }; + for (let index = 0; index < args.length; index += 1) { + if (args[index] === '--context-firewall' && args[index + 1]) { + options.contextFirewall = args[++index]; + } else if (args[index] === '--output' && args[index + 1]) options.output = args[++index]; + else throw new TypeError(`unknown or incomplete argument: ${args[index]}`); + } + return options; +} + +function gitHead(path) { + const result = spawnSync('git', ['-C', path, 'rev-parse', 'HEAD'], { encoding: 'utf8' }); + if (result.status !== 0) throw new Error(result.stderr.trim() || 'cannot read Context Firewall HEAD'); + return result.stdout.trim(); +} + +function expected(valid, classification, sufficiency, violationCodes = []) { + return { + classification, + sufficiency, + valid, + ...(violationCodes.length > 0 ? { violation_codes: violationCodes } : {}), + }; +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + const contextPath = resolve(options.contextFirewall); + const head = gitHead(contextPath); + if (head !== EXPECTED_CONTEXT_FIREWALL_SHA) { + throw new Error(`Context Firewall HEAD ${head} does not match ${EXPECTED_CONTEXT_FIREWALL_SHA}`); + } + const fixtures = await import(pathToFileURL(resolve(contextPath, 'fixtures/corpus.js'))); + const selected = [ + ['successful-reduced-run', 'normal/small-all-pass'], + ['failed-test-packet', 'failure/one'], + ['multiple-failures', 'failure/several'], + ['large-successful-suppression', 'normal/large-all-pass'], + ['valid-raw-evidence-reference', 'provenance/raw-reference-present'], + ['valid-needs-raw-evidence', 'process/interrupted-truncated'], + ['ambiguous-evidence-escalation', 'edge/malformed-output'], + ['ceiling-triggered-escalation', 'payload/critical-too-large'], + ['exact-payload-boundary', 'payload/exact-boundary'], + ]; + const validVectors = selected.map(([id, fixtureName]) => { + const fixture = fixtures.corpus.find((item) => item.name === fixtureName); + if (!fixture) throw new Error(`missing producer fixture ${fixtureName}`); + const execution = fixtures.executeFixture(fixture); + if (!execution.pass || !execution.packet) { + throw new Error(`producer fixture ${fixtureName} did not yield a valid packet`); + } + const packet = execution.packet; + const sufficiency = packet.decision_evidence.disposition; + return { + expected: expected( + true, + 'VALID', + sufficiency === 'SUFFICIENT' ? 'SUFFICIENT' : 'NEEDS_RAW_EVIDENCE', + ), + id, + packet, + producer_fixture: fixtureName, + source_input: fixture.input, + }; + }); + + const invalidVectors = [ + { + id: 'missing-required-field', base: 'successful-reduced-run', + packet_mutations: [{ op: 'remove', path: '/receipt/input_hash' }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['MALFORMED_SHA256']), + }, + { + id: 'unsupported-schema-version', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/protocol_version', value: 'opsle.context-firewall.evidence-packet/v2' }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['UNSUPPORTED_PACKET_VERSION']), + }, + { + id: 'malformed-hash', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/input_hash', value: 'sha256:not-a-digest' }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['MALFORMED_SHA256']), + }, + { + id: 'wrong-source-hash', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/input_hash', value: `sha256:${'0'.repeat(64)}` }], + refresh_reduced_bytes: true, + expected: expected(false, 'CRYPTOGRAPHIC_MISMATCH', 'INVALID', ['INPUT_HASH_MISMATCH']), + }, + { + id: 'wrong-reduced-packet-hash', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/semantic_payload_hash', value: `sha256:${'1'.repeat(64)}` }], + refresh_reduced_bytes: true, + expected: expected(false, 'CRYPTOGRAPHIC_MISMATCH', 'INVALID', ['SEMANTIC_PAYLOAD_HASH_MISMATCH']), + }, + { + id: 'tampered-source-bytes', base: 'successful-reduced-run', + source_mutations: [{ op: 'replace', path: '/streams/0/data', value: 'TAP version 13\nok 1 - tampered\n1..1\n# tests 1\n# pass 1\n# fail 0\n# skipped 0\n' }], + expected: expected(false, 'CRYPTOGRAPHIC_MISMATCH', 'INVALID', ['INPUT_HASH_MISMATCH']), + }, + { + id: 'impossible-byte-counts', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/measurements/original_bytes', value: 105 }], + refresh_reduced_bytes: true, + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['ORIGINAL_BYTE_COUNT_MISMATCH']), + }, + { + id: 'impossible-evidence-counts', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/measurements/original_event_count', value: 1 }], + refresh_reduced_bytes: true, + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['EVIDENCE_ACCOUNTING_MISMATCH']), + }, + { + id: 'contradictory-suppression-accounting', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/suppressed/categories/successful_test', value: 3 }], + refresh_reduced_bytes: true, + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['SUPPRESSION_CATEGORY_TOTAL_MISMATCH']), + }, + { + id: 'invalid-escalation-state', base: 'valid-needs-raw-evidence', + packet_mutations: [{ op: 'replace', path: '/receipt/raw_evidence/escalation_required', value: false }], + refresh_reduced_bytes: true, + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['ESCALATION_REQUIRED_MISMATCH']), + }, + { + id: 'negative-measurement', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/measurements/suppressed_evidence_count', value: -1 }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['INVALID_MEASUREMENT']), + }, + { + id: 'overflow-like-measurement', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/measurements/original_bytes', value: 9007199254740992 }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['INVALID_MEASUREMENT']), + }, + { + id: 'malformed-raw-evidence-locator', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/raw_evidence/reference', value: '' }], + expected: expected(false, 'STRUCTURALLY_INVALID', 'INVALID', ['INVALID_IDENTITY']), + }, + { + id: 'false-sufficiency-claim', base: 'valid-needs-raw-evidence', + packet_mutations: [{ op: 'replace', path: '/decision_evidence/disposition', value: 'SUFFICIENT' }], + refresh_semantic_hash: true, + refresh_reduced_bytes: true, + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['DISPOSITION_MISMATCH']), + }, + { + id: 'wrong-reduced-byte-measurement', base: 'successful-reduced-run', + packet_mutations: [{ op: 'replace', path: '/receipt/measurements/reduced_bytes', value: 1 }], + expected: expected(false, 'INTERNALLY_INCONSISTENT', 'INVALID', ['REDUCED_BYTE_COUNT_MISMATCH']), + }, + ]; + const output = { + context_firewall_revision: head, + invalid_vectors: invalidVectors, + protocol_version: 'opsle.decision-evidence.context-firewall-conformance/v1', + valid_vectors: validVectors, + }; + await writeFile(resolve(options.output), `${JSON.stringify(output, null, 2)}\n`, 'utf8'); + process.stdout.write(`${JSON.stringify({ output: resolve(options.output), vector_count: validVectors.length + invalidVectors.length })}\n`); +} + +main().catch((error) => { + process.stderr.write(`${JSON.stringify({ code: 'GENERATION_FAILED', message: error.message })}\n`); + process.exitCode = 1; +}); diff --git a/tools/verify-context-firewall-interop.js b/tools/verify-context-firewall-interop.js new file mode 100755 index 0000000..fb3776e --- /dev/null +++ b/tools/verify-context-firewall-interop.js @@ -0,0 +1,78 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { resolve } from 'node:path'; +import process from 'node:process'; +import { pathToFileURL } from 'node:url'; + +import { + canonicalJson, + validateContextFirewallPacket, +} from '../src/context-firewall-v1.js'; + +const EXPECTED_CONTEXT_FIREWALL_SHA = 'dd34bd9f681314761f1ca87f339648bf611811f3'; + +function gitHead(path) { + const result = spawnSync('git', ['-C', path, 'rev-parse', 'HEAD'], { encoding: 'utf8' }); + if (result.status !== 0) throw new Error(result.stderr.trim() || `cannot read Git HEAD at ${path}`); + return result.stdout.trim(); +} + +async function main() { + const contextPath = resolve(process.argv[2] ?? '../context-firewall'); + const contextHead = gitHead(contextPath); + if (contextHead !== EXPECTED_CONTEXT_FIREWALL_SHA) { + throw new Error(`Context Firewall HEAD ${contextHead} does not match ${EXPECTED_CONTEXT_FIREWALL_SHA}`); + } + const reducer = await import(pathToFileURL(resolve(contextPath, 'src/reducer.js'))); + const fixtures = await import(pathToFileURL(resolve(contextPath, 'fixtures/corpus.js'))); + const cases = [ + ['normal-success', 'normal/small-all-pass', 'SUFFICIENT'], + ['failure', 'failure/one', 'SUFFICIENT'], + ['suppression-heavy', 'normal/large-all-pass', 'SUFFICIENT'], + ['needs-raw-evidence', 'process/interrupted-truncated', 'NEEDS_RAW_EVIDENCE'], + ].map(([id, fixtureName, expectedSufficiency]) => { + const fixture = fixtures.corpus.find((item) => item.name === fixtureName); + const packet = reducer.reduceTestRun(fixture.input, fixture.options); + const packetBytes = reducer.serializePacket(packet); + const result = validateContextFirewallPacket(packet, { + packetBytes, + sourceInput: fixture.input, + }); + return { + classification: result.classification, + id, + pass: result.valid && result.sufficiency === expectedSufficiency, + producer_fixture: fixtureName, + sufficiency: result.sufficiency, + }; + }); + const tamperFixture = fixtures.corpus.find((item) => item.name === 'normal/small-all-pass'); + const tampered = reducer.reduceTestRun(tamperFixture.input); + tampered.receipt.input_hash = `sha256:${'0'.repeat(64)}`; + const tamperResult = validateContextFirewallPacket(tampered, { + packetBytes: Buffer.from(`${canonicalJson(tampered)}\n`, 'utf8'), + sourceInput: tamperFixture.input, + }); + cases.push({ + classification: tamperResult.classification, + id: 'tampered-copy', + pass: !tamperResult.valid + && tamperResult.violations.some((violation) => violation.code === 'INPUT_HASH_MISMATCH'), + sufficiency: tamperResult.sufficiency, + }); + const report = { + case_count: cases.length, + cases, + conformance: cases.every((item) => item.pass) ? 'PASS' : 'FAIL', + context_firewall_revision: contextHead, + decision_evidence_revision: gitHead(resolve('.')), + protocol_version: 'opsle.decision-evidence.context-firewall-interop/v1', + }; + process.stdout.write(`${canonicalJson(report)}\n`); + process.exitCode = report.conformance === 'PASS' ? 0 : 1; +} + +main().catch((error) => { + process.stderr.write(`${canonicalJson({ code: 'INTEROP_FAILED', message: error.message })}\n`); + process.exitCode = 1; +});