Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm run check
- run: npm test
- run: npm run conformance
14 changes: 14 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,20 @@ caller-owned raw stdout/stderr
v v
sufficient NEEDS_RAW_EVIDENCE
packet packet/control error
|
+--> canonical packet (stdout/model channel)
|
+--> sibling value receipt (API/optional sidecar)
|
+--> [Context Firewall] indicator (stderr/operator channel)
```

## Modules

- `src/reducer.js`: validation, byte framing, parsing, classification, policy,
hashing, measurement, and canonical serialization.
- `src/value-receipt.js`: dependency-free `opsle.value-receipt.v1` construction
and deterministic operator-indicator formatting.
- `bin/context-firewall.js`: stdin/file CLI and deterministic conformance entry.
- `fixtures/corpus.js`: synthetic public-safe fixture definitions and expected
decisions.
Expand All @@ -30,6 +38,12 @@ caller-owned raw stdout/stderr
The core has no host adapter or external package dependency. Supporting Opsle
protocols can consume the JSON fields without importing this package.

The value receipt is deliberately not embedded in the evidence packet. API
callers receive it as a sibling result and CLI callers may request a deterministic
sidecar. Canonical stdout therefore keeps the original compact model-context
boundary. The one named stderr indicator is operator telemetry; callers must not
merge it into model context automatically.

## Evidence ownership

The caller owns storage and addressability of raw bytes. The reducer binds those
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 0.3.0 - 2026-08-25

- Added a dependency-free sibling `opsle.value-receipt.v1` for reductions with
exact byte/event measurements and observed escalation/raw-locator state.
- Added `reduceWithValueReceipt()` and an optional deterministic
`--value-receipt` sidecar without increasing model-visible packet stdout.
- Added caller-supplied `--mechanism-revision` provenance that affects only the
receipt and never inspects ambient Git state.
- Added one named `[Context Firewall]` completion indicator on stderr plus
receipt, expansion, aggregation, trust, zero-baseline, channel-separation,
conformance, and static-check gates.
- This remains prototype evidence, not EXP-001 or a correctness, cost, latency,
token, or causal claim.

## 0.2.0 - 2026-08-25

- Added a dependency-free deterministic TAP-subset reducer and CLI.
Expand Down
39 changes: 38 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ safely not see?** This repository does not yet answer it.

## Prototype scope

Version 0.2.0 is a dependency-free Node.js reference reducer for a documented
Version 0.3.0 is a dependency-free Node.js reference reducer for a documented
flat TAP-compatible test-output subset. It:

- reads caller-supplied stdout and stderr bytes plus process metadata;
Expand All @@ -24,6 +24,9 @@ flat TAP-compatible test-output subset. It:
informational notes;
- retains ambiguous or malformed evidence and requires raw-evidence escalation;
- emits canonical JSON with source/configuration hashes and payload measurements;
- derives a sibling `opsle.value-receipt.v1` without adding it to model-visible
stdout;
- emits one named operator completion indicator on stderr;
- applies deterministic payload ceilings without silently truncating critical
evidence.

Expand Down Expand Up @@ -116,6 +119,40 @@ calculate visible fraction and reduction ratio without parsing human logs.
Runtime latency is intentionally absent from the hashed packet because it is
nondeterministic; callers may measure it outside the packet.

## Visible Value receipt and operator channel

`reduceWithValueReceipt(input, options)` returns `{ packet, valueReceipt }` while
`reduceTestRun(input, options)` remains packet-only. The value receipt uses
`opsle.value-receipt.v1` and exposes raw/model-visible bytes, signed initially
avoided bytes, an exact rational reduction ratio, original/retained/suppressed/
ambiguous event counts, payload ceiling, escalation, and raw-locator state.
Byte evidence makes no token, cost, latency, correctness, or causal claim.

The CLI always writes only the compact canonical evidence packet to stdout and
one named completion indicator to stderr:

```text
[Context Firewall] 28,981 B -> 1,846 B | 27,135 B initially avoided (93.63%) | escalation: no
```

To persist the deterministic receipt separately, the caller may request a
sidecar:

```bash
node ./bin/context-firewall.js reduce \
--mechanism-revision REVISION \
--value-receipt value-receipt.json
```

The caller must keep stderr and the optional receipt sidecar outside initial
decision-relevant model context. Invocation failures retain machine-readable
stderr and emit no success indicator. A negative avoided-byte delta is reported
as packet expansion rather than fabricated savings; ratios are not directly
summable.

`--mechanism-revision` is caller supplied, affects only the sidecar, and defaults
to `null`; the deterministic reducer never inspects ambient Git state.

## Deterministic retention policy

Classification is strict and case-sensitive after ANSI is removed for parsing.
Expand Down
32 changes: 32 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,34 @@ Object keys are serialized in lexical order with one final newline. Arrays retai
source order. No time, latency, random ID, filesystem state, locale, or ambient
environment value enters canonical output.

## Visible Value contract

The packet itself remains the compact model-visible output. A caller may derive a
sibling `opsle.value-receipt.v1` with `reduceWithValueReceipt()` or request a
canonical CLI sidecar with `--value-receipt`. The receipt is not embedded in the
packet and does not increase model-visible stdout.

The mechanism identity is `opsle.context-firewall`, the operation is
`test-output-reduction`, and the receipt contains `raw_bytes`,
`initial_model_visible_bytes`, `bytes_initially_avoided`,
`initial_reduction_ratio`, `original_evidence_events`,
`retained_evidence_events`, `suppressed_evidence_events`,
`ambiguous_evidence_events`, `payload_ceiling_bytes`, `escalation_required`, and
`raw_locator_available`.

Byte and event measurements are `EXACT`; escalation and raw-locator state are
`OBSERVED`. The avoided-byte delta is raw minus visible bytes and may be negative
for packet expansion. The ratio is an exact signed numerator/denominator string
and is not directly summable. A raw locator is caller supplied and is not proof
that the external artifact exists or was verified. Byte evidence supports no
token, cost, latency, correctness, or causal claim.

An exact mechanism revision may be caller supplied. It affects only the sibling
receipt and defaults to `null`; ambient repository state is never inspected.
Successful CLI reductions write only the canonical packet to stdout and one
named `[Context Firewall]` indicator to stderr. Invocation failures retain their
machine-readable stderr behavior and emit no success indicator.

## Evidence taxonomy

Source lines have exactly one class:
Expand Down Expand Up @@ -75,6 +103,10 @@ otherwise valid test names or explicit notes have no special meaning.
sufficient packet.
7. `measurements.reduced_bytes` equals the canonical serialized packet length.
8. The input hash binds stream names, lengths, order, and exact bytes.
9. Value-receipt visible bytes equal the final serialized packet length; deriving
or writing the receipt never changes packet bytes.
10. Operator telemetry is derived from the completed sibling receipt and remains
outside canonical stdout.

## Payload policy

Expand Down
24 changes: 19 additions & 5 deletions bin/context-firewall.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
#!/usr/bin/env node
import { readFile } from 'node:fs/promises';
import { readFile, writeFile } from 'node:fs/promises';
import process from 'node:process';
import { conformanceReport } from '../fixtures/corpus.js';
import {
InputError,
PayloadCeilingError,
canonicalJson,
reduceTestRun,
reduceWithValueReceipt,
serializePacket,
} from '../src/reducer.js';
import { formatContextFirewallIndicator } from '../src/value-receipt.js';

function usage() {
return 'usage: context-firewall reduce [--input PATH|-] [--max-bytes N]\n context-firewall conformance\n';
return 'usage: context-firewall reduce [--input PATH|-] [--max-bytes N] [--mechanism-revision REV] [--value-receipt PATH]\n context-firewall conformance\n';
}

function parseReduceArgs(args) {
let input = '-';
let maxOutputBytes = null;
let mechanismRevision = null;
let valueReceiptPath = null;
for (let index = 0; index < args.length; index += 1) {
if (args[index] === '--input' && args[index + 1]) input = args[++index];
else if (args[index] === '--max-bytes' && args[index + 1]) {
maxOutputBytes = Number(args[++index]);
} else if (args[index] === '--mechanism-revision' && args[index + 1]) {
mechanismRevision = args[++index];
} else if (args[index] === '--value-receipt' && args[index + 1]) {
valueReceiptPath = args[++index];
} else throw new InputError(`unknown or incomplete argument: ${args[index]}`);
}
return { input, maxOutputBytes };
return { input, maxOutputBytes, mechanismRevision, valueReceiptPath };
}

async function readInput(path) {
Expand Down Expand Up @@ -55,8 +62,15 @@ async function main() {
} catch {
throw new InputError('input must be valid JSON');
}
const packet = reduceTestRun(input, { maxOutputBytes: options.maxOutputBytes });
const { packet, valueReceipt } = reduceWithValueReceipt(input, {
maxOutputBytes: options.maxOutputBytes,
mechanismRevision: options.mechanismRevision,
});
if (options.valueReceiptPath) {
await writeFile(options.valueReceiptPath, `${canonicalJson(valueReceipt)}\n`, 'utf8');
}
process.stdout.write(serializePacket(packet));
process.stderr.write(`${formatContextFirewallIndicator(valueReceipt)}\n`);
}

main().catch((error) => {
Expand Down
24 changes: 21 additions & 3 deletions fixtures/corpus.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { INPUT_PROTOCOL, PayloadCeilingError, reduceTestRun, serializePacket } from '../src/reducer.js';
import {
INPUT_PROTOCOL,
PayloadCeilingError,
reduceTestRun,
reduceWithValueReceipt,
serializePacket,
} from '../src/reducer.js';

function tap({ passed = [], failed = [], skipped = [], extras = [], finalNewline = true }) {
const lines = ['TAP version 13'];
Expand Down Expand Up @@ -91,7 +97,7 @@ export function executeFixture(fixture) {
? { maxOutputBytes: exactCeiling(fixture.input) }
: (fixture.options ?? {});
try {
const packet = reduceTestRun(fixture.input, options);
const { packet, valueReceipt } = reduceWithValueReceipt(fixture.input, options);
const bytes = serializePacket(packet);
const evidence = packet.decision_evidence;
const expected = fixture.expected;
Expand Down Expand Up @@ -120,10 +126,20 @@ export function executeFixture(fixture) {
);
if (options.maxOutputBytes != null) checks.push(bytes.length <= options.maxOutputBytes);
checks.push(packet.receipt.measurements.reduced_bytes === bytes.length);
checks.push(valueReceipt.schema === 'opsle.value-receipt.v1');
checks.push(valueReceipt.measurements.find(
(measurement) => measurement.id === 'raw_bytes',
)?.result === packet.receipt.measurements.original_bytes);
checks.push(valueReceipt.measurements.find(
(measurement) => measurement.id === 'initial_model_visible_bytes',
)?.result === bytes.length);
checks.push(valueReceipt.measurements.find(
(measurement) => measurement.id === 'escalation_required',
)?.result === packet.receipt.raw_evidence.escalation_required);
checks.push(packet.receipt.measurements.original_bytes === fixture.input.streams.reduce(
(sum, stream) => sum + Buffer.byteLength(stream.data, stream.encoding === 'base64' ? 'base64' : 'utf8'), 0,
));
return { packet, options, pass: checks.every(Boolean) };
return { packet, valueReceipt, options, pass: checks.every(Boolean) };
} catch (error) {
if (error instanceof PayloadCeilingError && fixture.expected.ceilingError) {
return { error, options, pass: true };
Expand Down Expand Up @@ -168,12 +184,14 @@ export function conformanceReport() {
verdict_preserved: fixture.expected.status == null
? null
: packet.decision_evidence.status === fixture.expected.status,
value_receipt_schema: result.valueReceipt.schema,
};
});
return {
conformance: fixtures.every((fixture) => fixture.conformance === 'PASS') ? 'PASS' : 'FAIL',
fixture_count: fixtures.length,
fixtures,
protocol_version: 'opsle.context-firewall.conformance/v1',
value_receipt_schema: 'opsle.value-receipt.v1',
};
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@opsle/context-firewall",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"type": "module",
"bin": {
"context-firewall": "./bin/context-firewall.js"
},
"scripts": {
"check": "node --check ./src/reducer.js && node --check ./src/value-receipt.js && node --check ./bin/context-firewall.js && node --check ./fixtures/corpus.js && node --check ./tests/reducer.test.js",
"test": "node --test",
"conformance": "node ./bin/context-firewall.js conformance"
},
Expand Down
38 changes: 37 additions & 1 deletion src/reducer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { createHash } from 'node:crypto';
import { TextDecoder } from 'node:util';
import { buildReductionValueReceipt } from './value-receipt.js';

export const INPUT_PROTOCOL = 'opsle.context-firewall.test-run-input/v1';
export const PACKET_PROTOCOL = 'opsle.context-firewall.evidence-packet/v1';
export const REDUCER_NAME = '@opsle/context-firewall/test-output';
export const REDUCER_VERSION = '0.2.0';
export const REDUCER_VERSION = '0.3.0';
export const POLICY_REVISION = 'tap-subset-policy/v1';

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;
Expand Down Expand Up @@ -516,3 +517,38 @@ export function reduceTestRun(input, options = {}) {
export function serializePacket(packet) {
return Buffer.from(`${canonicalJson(packet)}\n`, 'utf8');
}

export function valueReceiptForPacket(packet, { mechanismRevision = null } = {}) {
if (mechanismRevision != null && (typeof mechanismRevision !== 'string' || mechanismRevision.length === 0)) {
throw new InputError('mechanismRevision must be a nonempty string or null');
}
const measurements = packet.receipt.measurements;
return buildReductionValueReceipt({
ambiguousEvents: packet.decision_evidence.unclassified_evidence.length,
configurationIdentity: packet.receipt.configuration.identity,
escalationRequired: packet.receipt.raw_evidence.escalation_required,
inputHash: packet.receipt.input_hash,
mechanismRevision,
mechanismVersion: REDUCER_VERSION,
operationId: packet.operation_id,
originalBytes: measurements.original_bytes,
originalEvents: measurements.original_event_count,
payloadCeilingBytes: packet.receipt.configuration.max_output_bytes,
policyRevision: packet.receipt.configuration.policy_revision,
rawEvidenceRef: packet.receipt.raw_evidence.reference,
reducedBytes: measurements.reduced_bytes,
retainedEvents: measurements.retained_evidence_count,
runId: packet.receipt.source.run_id,
semanticPayloadHash: packet.receipt.semantic_payload_hash,
});
}

export function reduceWithValueReceipt(input, options = {}) {
const packet = reduceTestRun(input, options);
return {
packet,
valueReceipt: valueReceiptForPacket(packet, {
mechanismRevision: options.mechanismRevision ?? null,
}),
};
}
Loading
Loading