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
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,10 @@ jobs:

mkdir -p .coverage-output
while IFS= read -r report; do
artifact_name="${report//\//_}"
# The aggregate lane and the patch-coverage gate both collect
# *.lcov.info, so the suffix has to be a dot — a flattened
# "..._coverage_lcov.info" name is silently ignored by both.
artifact_name="$(printf '%s' "${report%/coverage/lcov.info}" | tr '/' '_').lcov.info"
cp "$report" ".coverage-output/${artifact_name}"
done < <(find packages -type f -path '*/coverage/lcov.info' | sort)
echo "Coverage-capable packages are tested in this lane; packages without"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Install deps
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Build TS runner dependencies
run: pnpm -r --filter '@bsv/wallet-toolbox...' run build
run: pnpm -r --filter '@bsv/wallet-toolbox...' --filter '@bsv/air-gap' run build
- name: Check TS conformance runner quality
run: |
pnpm --filter @bsv/conformance-runner-ts format:check
Expand Down
70 changes: 20 additions & 50 deletions conformance/META.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"auth",
"payments",
"storage",
"sync"
"sync",
"transport"
],
"brc_coverage": {
"BRC-42": [
Expand All @@ -19,22 +20,14 @@
"sdk.keys.publickey",
"sdk.crypto.signature"
],
"BRC-74": [
"sdk.transactions.merklepath",
"broadcast.merklepath"
],
"BRC-77": [
"sdk.compat.bsm"
],
"BRC-74": ["sdk.transactions.merklepath", "broadcast.merklepath"],
"BRC-77": ["sdk.compat.bsm"],
"BRC-31": [
"messaging.brc31.authrite-signature",
"auth.brc31-handshake",
"messaging.authsocket"
],
"BRC-29": [
"wallet.brc29.payment-derivation",
"payments.brc29-payment-protocol"
],
"BRC-29": ["wallet.brc29.payment-derivation", "payments.brc29-payment-protocol"],
"BRC-100": [
"wallet.brc100.getpublickey",
"wallet.brc100.createhmac",
Expand Down Expand Up @@ -66,46 +59,23 @@
"wallet.brc100.getversion",
"wallet.storage.adapterconformance"
],
"BRC-121": [
"payments.brc121"
],
"BRC-26": [
"storage.uhrp-http"
],
"BRC-62": [
"overlay.submit"
],
"BRC-22": [
"overlay.lookup",
"overlay.topicmanagement"
],
"BRC-20": [
"broadcast.arcsubmit",
"broadcast.merklepath"
],
"BRC-21": [
"sync.gasprotocol"
],
"BRC-40": [
"sync.brc40"
],
"BRC-14": [
"sdk.scripts.evaluation"
],
"merkle-service": [
"broadcast.merkle-service"
],
"message-box": [
"messaging.messagebox-http"
],
"chaintracks-v2": [
"sync.chaintracks-v2-http"
]
"BRC-121": ["payments.brc121"],
"BRC-26": ["storage.uhrp-http"],
"BRC-62": ["overlay.submit"],
"BRC-22": ["overlay.lookup", "overlay.topicmanagement"],
"BRC-20": ["broadcast.arcsubmit", "broadcast.merklepath"],
"BRC-21": ["sync.gasprotocol"],
"BRC-40": ["sync.brc40"],
"BRC-14": ["sdk.scripts.evaluation"],
"merkle-service": ["broadcast.merkle-service"],
"message-box": ["messaging.messagebox-http"],
"chaintracks-v2": ["sync.chaintracks-v2-http"],
"BRC-141": ["transport.air-gap-optical"]
},
"stats": {
"total_files": 74,
"total_vectors": 6650,
"last_updated": "2026-07-27"
"total_files": 75,
"total_vectors": 6681,
"last_updated": "2026-07-30"
},
"regression_index": {
"beef-v2-txid-panic": "go-sdk#306",
Expand Down
24 changes: 18 additions & 6 deletions conformance/PARITY_MATRIX.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"schema_version": "1.0",
"generated_at": "2026-07-27",
"generated_at": "2026-07-30",
"source": "ts-stack conformance corpus",
"description": "Machine-readable parity status for cross-language SDK implementations (Go, Rust, Python). Use this to track and drive conformance.",
"summary": {
"total_files": 74,
"total_vectors": 6650,
"fully_required_files": 55,
"total_files": 75,
"total_vectors": 6681,
"fully_required_files": 56,
"files_with_intended": 17,
"files_with_mixed_status": 15,
"vectors_by_status": {
"required": 6446,
"required": 6477,
"intended": 204,
"skipped": 7
},
"by_reason_category": {
"fully_supported": 1234,
"fully_supported": 1265,
"governed_vector_skip": 50,
"historical_regression": 36,
"partial_ts_behavioral_difference": 5116,
Expand Down Expand Up @@ -568,6 +568,18 @@
"reason_category": "fully_supported",
"categories": []
},
{
"path": "transport/air-gap-optical.json",
"id": "transport.air-gap-optical",
"total_vectors": 31,
"file_level_parity": "required",
"effective_status": "required",
"required_count": 31,
"intended_count": 0,
"skipped_count": 0,
"reason_category": "fully_supported",
"categories": []
},
{
"path": "wallet/brc100/abortaction.json",
"id": "wallet.brc100.abortaction",
Expand Down
114 changes: 114 additions & 0 deletions conformance/runner/ts/dispatchers/transport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Transport dispatcher — air-gap optical transport (BRC-141).
*
* Categories:
* air-gap-optical — wire protocol v1 of @bsv/air-gap: deterministic
* fountain-coded part encoding, camera-safe decoding, session locking,
* and hostile-input rejection.
*
* Vector operations (input.operation):
* crc32 — IEEE CRC-32 check value of a hex payload
* part-char-length — exact rendered part length for a block size
* encode-part — one deterministic part string for
* (message, blockBytes, sessionId, seq)
* decode — feed parts in order; the message must complete and
* match the expected hex exactly
* progress — feed parts in order; assert final progress counters
* (used for the linear-dependence stall regression)
* accept-one — a single scanned string must be rejected without a
* throw and without producing a message
*
* Binary data is lowercase hex in the vectors (corpus convention); part
* strings travel verbatim since they are the wire format under test.
*/

import { expect } from '@jest/globals'
import { AirGapDecoder, AirGapEncoder, crc32, estimatePartCharLength } from '@bsv/air-gap'

export const categories: ReadonlyArray<string> = ['air-gap-optical']

function hexToBytes(hex: string): Uint8Array {
const out = new Uint8Array(hex.length / 2)
for (let i = 0; i < out.length; i++) {
out[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16)
}
return out
}

function bytesToHex(bytes: Uint8Array): string {
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}

function getString(m: Record<string, unknown>, key: string): string {
const v = m[key]
return typeof v === 'string' ? v : ''
}

function getNumber(m: Record<string, unknown>, key: string): number {
const v = m[key]
return typeof v === 'number' ? v : Number.NaN
}

function getStrings(m: Record<string, unknown>, key: string): string[] {
const v = m[key]
return Array.isArray(v) ? v.filter((item): item is string => typeof item === 'string') : []
}

export function dispatch(
category: string,
input: Record<string, unknown>,
expected: Record<string, unknown>
): void {
if (category !== 'air-gap-optical') {
throw new Error(`not implemented: transport category '${category}'`)
}
const operation = getString(input, 'operation')
switch (operation) {
case 'crc32': {
const value = crc32(hexToBytes(getString(input, 'message_hex')))
expect(value.toString(16).padStart(8, '0')).toBe(expected.crc32_hex)
return
}
case 'part-char-length': {
expect(estimatePartCharLength(getNumber(input, 'block_bytes'))).toBe(expected.chars)
return
}
case 'encode-part': {
const encoder = new AirGapEncoder(hexToBytes(getString(input, 'message_hex')), {
blockBytes: getNumber(input, 'block_bytes'),
sessionId: hexToBytes(getString(input, 'session_id_hex'))
})
expect(encoder.partAt(getNumber(input, 'seq'))).toBe(expected.part)
return
}
case 'decode': {
const decoder = new AirGapDecoder()
let done = false
for (const part of getStrings(input, 'parts')) done = decoder.accept(part).done || done
expect(done).toBe(true)
const out = decoder.message()
expect(out).not.toBeNull()
expect(bytesToHex(out as Uint8Array)).toBe(expected.message_hex)
return
}
case 'progress': {
const decoder = new AirGapDecoder()
let last = decoder.accept('')
for (const part of getStrings(input, 'parts')) last = decoder.accept(part)
expect(last.have).toBe(expected.have)
expect(last.total).toBe(expected.total)
expect(last.done).toBe(expected.done)
return
}
case 'accept-one': {
const decoder = new AirGapDecoder()
expect(decoder.accept(getString(input, 'text')).ok).toBe(expected.ok)
expect(decoder.message()).toBeNull()
return
}
default:
throw new Error(`not implemented: transport operation '${operation}'`)
}
}
1 change: 1 addition & 0 deletions conformance/runner/ts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"typecheck": "tsc --noEmit --pretty false"
},
"devDependencies": {
"@bsv/air-gap": "workspace:^",
"@bsv/sdk": "workspace:^",
"@jest/globals": "^30.4.1",
"@types/node": "^26.1.2",
Expand Down
7 changes: 5 additions & 2 deletions conformance/runner/ts/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as overlay from './dispatchers/overlay.js'
import * as payments from './dispatchers/payments.js'
import * as storage from './dispatchers/storage.js'
import * as sync from './dispatchers/sync.js'
import * as transport from './dispatchers/transport.js'
import * as walletStorage from './dispatchers/wallet-storage.js'

export type DispatchFn = (
Expand Down Expand Up @@ -56,7 +57,8 @@ const DISPATCHERS: Array<{
{ domain: 'overlay', dispatcher: overlay },
{ domain: 'payments', dispatcher: payments },
{ domain: 'storage', dispatcher: storage },
{ domain: 'sync', dispatcher: sync }
{ domain: 'sync', dispatcher: sync },
{ domain: 'transport', dispatcher: transport }
]

const CATEGORY_MAP = new Map<string, Route>()
Expand Down Expand Up @@ -84,7 +86,8 @@ const PREFIX_MAP: Array<[string, Route]> = [
['overlay.', { domain: 'overlay', dispatch: overlay.dispatch }],
['payments.', { domain: 'payments', dispatch: payments.dispatch }],
['storage.', { domain: 'storage', dispatch: storage.dispatch }],
['sync.', { domain: 'sync', dispatch: sync.dispatch }]
['sync.', { domain: 'sync', dispatch: sync.dispatch }],
['transport.', { domain: 'transport', dispatch: transport.dispatch }]
]

/**
Expand Down
Loading
Loading