From 7248cdf029bf15e1ca5d5af0664de85aac7747ae Mon Sep 17 00:00:00 2001 From: Sungman Cho Date: Fri, 14 Aug 2026 19:59:20 +0900 Subject: [PATCH 1/3] feat: freeze OGLO diagnostics wire contract --- spec/diagnostics_v1/README.md | 19 + spec/diagnostics_v1/cases.json | 1 + spec/diagnostics_v1/contract.json | 1 + spec/diagnostics_v1/manifest.json | 1 + spec/diagnostics_v1/vectors/boot_summary.bin | Bin 0 -> 160 bytes .../vectors/boot_summary.expected.json | 1 + .../diagnostics_v1/vectors/device_context.bin | Bin 0 -> 160 bytes .../vectors/device_context.expected.json | 1 + spec/diagnostics_v1/vectors/imu.bin | Bin 0 -> 86 bytes spec/diagnostics_v1/vectors/imu.expected.json | 1 + spec/diagnostics_v1/vectors/mag.bin | Bin 0 -> 62 bytes spec/diagnostics_v1/vectors/mag.expected.json | 1 + spec/diagnostics_v1/vectors/start_ack.bin | Bin 0 -> 162 bytes .../vectors/start_ack.expected.json | 1 + spec/diagnostics_v1/vectors/stop_ack.bin | Bin 0 -> 46 bytes .../vectors/stop_ack.expected.json | 1 + .../vectors/stream_evidence.bin | Bin 0 -> 231 bytes .../vectors/stream_evidence.expected.json | 1 + .../diagnostics_v1/vectors/stream_failure.bin | Bin 0 -> 68 bytes .../vectors/stream_failure.expected.json | 1 + spec/diagnostics_v1/vectors/tactile.bin | Bin 0 -> 149 bytes .../vectors/tactile.expected.json | 1 + tests/test_diagnostics_contract.py | 242 ++++++++ tools/generate_diagnostics_contract.py | 522 ++++++++++++++++++ 24 files changed, 795 insertions(+) create mode 100644 spec/diagnostics_v1/README.md create mode 100644 spec/diagnostics_v1/cases.json create mode 100644 spec/diagnostics_v1/contract.json create mode 100644 spec/diagnostics_v1/manifest.json create mode 100644 spec/diagnostics_v1/vectors/boot_summary.bin create mode 100644 spec/diagnostics_v1/vectors/boot_summary.expected.json create mode 100644 spec/diagnostics_v1/vectors/device_context.bin create mode 100644 spec/diagnostics_v1/vectors/device_context.expected.json create mode 100644 spec/diagnostics_v1/vectors/imu.bin create mode 100644 spec/diagnostics_v1/vectors/imu.expected.json create mode 100644 spec/diagnostics_v1/vectors/mag.bin create mode 100644 spec/diagnostics_v1/vectors/mag.expected.json create mode 100644 spec/diagnostics_v1/vectors/start_ack.bin create mode 100644 spec/diagnostics_v1/vectors/start_ack.expected.json create mode 100644 spec/diagnostics_v1/vectors/stop_ack.bin create mode 100644 spec/diagnostics_v1/vectors/stop_ack.expected.json create mode 100644 spec/diagnostics_v1/vectors/stream_evidence.bin create mode 100644 spec/diagnostics_v1/vectors/stream_evidence.expected.json create mode 100644 spec/diagnostics_v1/vectors/stream_failure.bin create mode 100644 spec/diagnostics_v1/vectors/stream_failure.expected.json create mode 100644 spec/diagnostics_v1/vectors/tactile.bin create mode 100644 spec/diagnostics_v1/vectors/tactile.expected.json create mode 100644 tests/test_diagnostics_contract.py create mode 100644 tools/generate_diagnostics_contract.py diff --git a/spec/diagnostics_v1/README.md b/spec/diagnostics_v1/README.md new file mode 100644 index 0000000..fc47c49 --- /dev/null +++ b/spec/diagnostics_v1/README.md @@ -0,0 +1,19 @@ +# OGLO diagnostics v1 contract + +Status: **implementation-contract-not-hardware-captured**. + +This is the Phase 0 schema/vector freeze only. It makes no runtime parser, USB, +firmware, or SDK behavior change. `contract.json` freezes TAG1, TAG2, typed control +frames, admission/state rules, CONTEXT canonicalization, and the canonical manifest +preimage. `manifest.json` has an explicit `contract_inputs` hash view and a separate +complete generated-vector digest inventory, avoiding START_ACK hash self-reference. + +Existing `spec/vectors/tag_*.bin` files have `physical_capture` provenance. All +files in `vectors/` are deterministic synthetic conformance evidence, not board +captures. At default rates, TAG2 estimates 49,937.5 B/s (about 49,938): tactile +250 Hz plus IMU 500 Hz batched by 4 plus MAG 125 Hz batched by 4, excluding boundary +flush partial batches/control frames. This is an unqualified estimate, not a +throughput PASS: real hardware jitter and throughput capture remain required. + +Regenerate with `python tools/generate_diagnostics_contract.py`; verify without +writing with `python tools/generate_diagnostics_contract.py --check`. diff --git a/spec/diagnostics_v1/cases.json b/spec/diagnostics_v1/cases.json new file mode 100644 index 0000000..9f20f98 --- /dev/null +++ b/spec/diagnostics_v1/cases.json @@ -0,0 +1 @@ +{"commands":{"fatal_order":["producer_boundary","drain_accepted_frames","STREAM_FAILURE","STREAM_EVIDENCE"],"fatal_stop_ack":"forbidden","invalid_hex":["53545245414d2054414732204f4e206e6f6e63653d3030313132323333343435353636373738383939616162626363646465656666","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660d0a","73747265616d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d205441473220204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839394141424243434444454546460a","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566662065787472610a"],"transitions":[{"active_nonce":"","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"STARTING","reopen":false,"response":"new_start"},"lifecycle":"IDLE","name":"new_start","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"cached_start_ack"},"lifecycle":"ACTIVE","name":"same_start_active","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4e206e6f6e63653d66666565646463636262616139393838373736363535343433333232313130300a","expected":{"accepted":false,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"nonce_conflict"},"lifecycle":"ACTIVE","name":"different_start_active","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"STOPPING","reopen":false,"response":"stop_or_cached_stop_ack"},"lifecycle":"ACTIVE","name":"matching_stop","retired_nonces":[]},{"active_nonce":"","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":false,"lifecycle":"IDLE","reopen":false,"response":"nonce_retired"},"lifecycle":"IDLE","name":"retired_start","retired_nonces":["00112233445566778899aabbccddeeff"]},{"active_nonce":"","command_hex":"53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"lifecycle":"IDLE","reopen":false,"response":"cached_stop_ack"},"lifecycle":"IDLE","name":"cached_stop","retired_nonces":["00112233445566778899aabbccddeeff"]},{"active_nonce":"","command_hex":"53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a","expected":{"accepted":true,"lifecycle":"IDLE","reopen":false,"response":"context_ack"},"lifecycle":"IDLE","name":"context_idle","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a","expected":{"accepted":false,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"command_rejected"},"lifecycle":"ACTIVE","name":"context_active","retired_nonces":[]}],"valid_hex":["53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a"]},"contexts":{"calibration_hash_golden":{"canonical_view_utf8":"{\"imu_bias_i16\":[0,0,0,0,0,0],\"mag_bias_i16\":[0,0,0],\"schema\":\"oglo.calibration_view.v1\",\"tactile_baseline_u16\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tactile_threshold_u16\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}","domain_separator_hex":"4f474c4f2d43414c4942524154494f4e2d563100","preimage_hex":"4f474c4f2d43414c4942524154494f4e2d5631007b22696d755f626961735f693136223a5b302c302c302c302c302c305d2c226d61675f626961735f693136223a5b302c302c305d2c22736368656d61223a226f676c6f2e63616c6962726174696f6e5f766965772e7631222c2274616374696c655f626173656c696e655f753136223a5b302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c305d2c2274616374696c655f7468726573686f6c645f753136223a5b302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c305d7d","sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6"},"config_hash_golden":{"canonical_view_utf8":"{\"firmware_schema\":6,\"firmware_semver\":{\"major\":0,\"minor\":9,\"patch\":12},\"hardware_model_id\":1330072655,\"imu\":{\"axes\":[\"ax\",\"ay\",\"az\",\"gx\",\"gy\",\"gz\"],\"rate_hz\":500,\"raw\":\"i16le\"},\"mag\":{\"axes\":[\"mx\",\"my\",\"mz\"],\"rate_hz\":125,\"raw\":\"i16le\"},\"schema\":\"oglo.config_view.v1\",\"tactile\":{\"order\":\"finger,row,col\",\"packing\":\"packed12\",\"rate_hz\":250,\"taxel_count\":80},\"tag_ver_max\":2}","domain_separator_hex":"4f474c4f2d434f4e4649472d563100","preimage_hex":"4f474c4f2d434f4e4649472d5631007b226669726d776172655f736368656d61223a362c226669726d776172655f73656d766572223a7b226d616a6f72223a302c226d696e6f72223a392c227061746368223a31327d2c2268617264776172655f6d6f64656c5f6964223a313333303037323635352c22696d75223a7b2261786573223a5b226178222c226179222c22617a222c226778222c226779222c22677a225d2c22726174655f687a223a3530302c22726177223a226931366c65227d2c226d6167223a7b2261786573223a5b226d78222c226d79222c226d7a225d2c22726174655f687a223a3132352c22726177223a226931366c65227d2c22736368656d61223a226f676c6f2e636f6e6669675f766965772e7631222c2274616374696c65223a7b226f72646572223a2266696e6765722c726f772c636f6c222c227061636b696e67223a227061636b65643132222c22726174655f687a223a3235302c22746178656c5f636f756e74223a38307d2c227461675f7665725f6d6178223a327d","sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2"},"context_sha256":"8f408c6edcb0453f97497c1b0785d036883d1f3b9200c0e4ce862639cde5a46e","hash_admission":{"matching":"accept","mismatch":"fatal_stream_failure_no_tag1_fallback"},"invalid":{"bad_epoch":"{\"context_epoch\":-1}","bad_hash":"{\"calibration_sha256\":\"xyz\"}","bad_utf8":"_w","non_nfc":"{\"schema\":\"oglo.device_context.v1\",\"recording_id\":\"e\\u0301\"}","non_url_safe":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn+","null":"{\"calibration_sha256\":null}","padded_base64url":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn0=","uppercase_hash":"{\"calibration_sha256\":\"A\"}"},"max_valid_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"recording_id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"schema\":\"oglo.device_context.v1\"}","max_valid_command_bytes":1379,"max_valid_token_bytes":1366,"max_valid_utf8_bytes":1024,"oversize_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"recording_id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"schema\":\"oglo.device_context.v1\"}","valid_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"schema\":\"oglo.device_context.v1\"}","valid_set_context_base64url":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn0"},"negotiation_cases":[{"derived":{"accepted":true,"fail_closed":false,"no_tag1_fallback":true,"reason":"accepted","stream_id":72623859790382856,"tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","lifecycle":"STARTING","malformed":0,"name":"start_positive","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_nonce","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010299999999999999999999999999999999102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555552a25e8e8","lifecycle":"STARTING","malformed":0,"name":"start_wrong_nonce","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_protocol_version","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010100112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555545e652e6","lifecycle":"STARTING","malformed":0,"name":"start_wrong_version","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_contract_sha256","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444447777777777777777777777777777777777777777777777777777777777777777cc95fb51","lifecycle":"STARTING","malformed":0,"name":"start_wrong_contract","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_context_sha256","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000006666666666666666666666666666666666666666666666666666666666666666555555555555555555555555555555555555555555555555555555555555555597818365","lifecycle":"STARTING","malformed":0,"name":"start_wrong_context","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"zero_boot_id","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff0000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555660e6b82","lifecycle":"STARTING","malformed":0,"name":"start_zero_boot","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"zero_stream_id","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000000000000000000000200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555d4fe2661","lifecycle":"STARTING","malformed":0,"name":"start_zero_stream","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"no_valid_start_ack","stream_id":"","tail_packets":[]},"input_hex":"a55b83850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555ab2beae7","lifecycle":"STARTING","malformed":1,"name":"start_wrong_first_type","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_lifecycle","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","lifecycle":"ACTIVE","malformed":0,"name":"start_wrong_lifecycle","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"no_valid_start_ack","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc1","lifecycle":"STARTING","malformed":1,"name":"start_crc","remainder_hex":""}],"parser_cases":[{"expected_admission":{"accepted":[2],"control_replays":0,"duplicates":0,"expected_seq":3,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":2,"input_hex":"a55b01780002000000030000000200000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422438acee419","name":"valid_data"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":2,"input_hex":"a55b01780002000000030000000200000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422438acee418","name":"crc_data_reject_continue"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc1","name":"crc_start_ack_malformed"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":1},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"1230066625199609624","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b01780000000000040000000200000018171615141312111f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f24024124224390104333","name":"wrong_stream_stale"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b63000000000000050000000200000008070605040302015be61f5e","name":"unknown_type"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b01010000000000060000000200000008070605040302010087013de7","name":"bad_type_length"},{"expected_admission":{"accepted":[0,1],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128},{"seq":1,"stream_id":"72623859790382856","type":131}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe0f0300030000004f4c474f020600090c11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333333333333307000000629e3116","name":"coalesced_start_boot"},{"expected_admission":{"accepted":[0],"control_replays":0,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128}],"expected_remainder_hex":"a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe","name":"coalesced_start_partial_boot"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a5","initial_expected_seq":0,"input_hex":"a5","name":"split_start_1"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b80850000000000010000000200000008070605040302","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302","name":"split_start_24"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b8085000000000001000000020000000807060504030201","initial_expected_seq":0,"input_hex":"a55b8085000000000001000000020000000807060504030201","name":"split_start_25"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444","name":"split_start_100"},{"expected_admission":{"accepted":[0,1],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":1,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800010000000b0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f24024124224350723e74","name":"normal"},{"expected_admission":{"accepted":[0,2],"control_replays":0,"duplicates":0,"expected_seq":3,"fail_closed":false,"gaps":1,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800020000000c0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422435da6917e","name":"forward_gap"},{"expected_admission":{"accepted":[0],"control_replays":0,"duplicates":1,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":0,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726","name":"duplicate"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":1,"expected_seq":3,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":3,"input_hex":"a55b017800020000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243415717fd","name":"out_of_order"},{"expected_admission":{"accepted":[4294967294,4294967295,0],"control_replays":0,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":4294967294,"stream_id":"72623859790382856","type":1},{"seq":4294967295,"stream_id":"72623859790382856","type":1},{"seq":0,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"a5","initial_expected_seq":4294967294,"input_hex":"a55b017800feffffff0a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422434bbef77aa55b017800ffffffff0b0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243e8926e28a55b017800000000000c0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243efaf21a5","name":"wrap"},{"expected_admission":{"accepted":[],"control_replays":1,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128}],"expected_remainder_hex":"","initial_expected_seq":1,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","name":"cached_start_ack"}],"status":"synthetic_conformance_cases_not_hardware_captures","timing":{"global_wire_header_monotonic_required":false,"imu_default_offsets_us":[0,2000,4000,6000],"imu_flush_deadline_ms":8,"mag_default_offsets_us":[0,8000,16000,24000],"mag_flush_deadline_ms":32,"per_modality_reconstructed_sample_monotonic_required":true}} diff --git a/spec/diagnostics_v1/contract.json b/spec/diagnostics_v1/contract.json new file mode 100644 index 0000000..ac69b1f --- /dev/null +++ b/spec/diagnostics_v1/contract.json @@ -0,0 +1 @@ +{"admission":{"active":"same START nonce retry returns byte-identical cached ACK/current stream; different nonce gives nonfatal nonce_conflict/no transition; only matching STOP accepted; other commands/context mutation nonfatal command_rejected; context/topology mutation uses fatal terminal order","capability":"GET CONFIG numeric tag_ver_max; missing is TAG1-only proof only for validated legacy firmware <=0.9.12/schema6; 1 selects TAG1; >=2 selects min(host_max,device_max); malformed_or_contradictory_fails_closed","command_encoding":"strict ASCII, exact uppercase and single spaces, one LF; CRLF/leading/trailing/extra tokens rejected","commands":{"context":"SET CONTEXT \n","start":"STREAM TAG2 ON nonce=<32 lowercase hex>\n","stop":"STREAM TAG2 OFF nonce=\n"},"host":"CRC-valid wrong-stream frame is stale evidence: discard and continue without changing current expected seq/time/counters; never TAG1 fallback","parser_resync":"scan negotiated magic only; retain incomplete frame exactly and one possible magic-prefix byte when no complete magic; invalid type/length/CRC advances scan by one byte after the first magic byte; never reinterpret TAG2 as TAG1","receiver_sequence":"expected seq modulo 2^32; exact accepts; forward distance <2^31 records gap then accepts; backward distance is duplicate/out-of-order; cached control replay neither advances nor gaps","start":"first valid frame after ON is START_ACK matching nonce, stream_id, contract_sha256; preserve fragment/coalesced tail; invalid ACK/CRC/wrong stream never downgrades","stop":"same STOP retry returns cached STOP_ACK within boot; retired START nonce cannot reopen; new nonce after terminal gets new stream_id"},"canonicalization":{"generated_vector_hashes":"inventory only; excluded from contract_sha256 preimage to avoid START_ACK self-reference","hash_preimage":"SHA256(b'OGLO-DIAGNOSTICS-CONTRACT-V1\\0' + canonical_json(manifest.contract_inputs))","json":"UTF-8,NFC,LF,object_keys_sorted,compact_separators,no_duplicate_keys,no_null,no_NaN,no_float,unknown_optional_omitted; runtime_u64_and_ns_use_decimal_strings_when_JSON"},"context":{"ack":"#CONTEXT ok sha256=<64 lowercase hex>\\n","active":"ACTIVE rejects mutation through typed STREAM_FAILURE command_rejected","calibration_sha256":{"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":"4f474c4f2d43414c4942524154494f4e2d563100","golden_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","view":{"additional_fields":false,"fields":{"imu_bias_i16":"array[6]","mag_bias_i16":"array[3]","tactile_baseline_u16":"array[80]","tactile_threshold_u16":"array[80]"},"schema":"oglo.calibration_view.v1"}},"config_sha256":{"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":"4f474c4f2d434f4e4649472d563100","golden_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","view":{"additional_fields":false,"fields":{"firmware_schema":"u8","firmware_semver":"{major:u8,minor:u8,patch:u8}","hardware_model_id":"u32","imu":{"axes":"array[ax,ay,az,gx,gy,gz]","rate_hz":"u16","raw":"i16le"},"mag":{"axes":"array[mx,my,mz]","rate_hz":"u16","raw":"i16le"},"tactile":{"order":"finger,row,col","packing":"packed12","rate_hz":"u16","taxel_count":"80"},"tag_ver_max":"u8"},"schema":"oglo.config_view.v1"}},"context_sha256":{"algorithm":"SHA256","preimage":"canonical UTF-8 bytes without LF"},"epoch_ownership":{"config_epoch":"firmware: starts 1 after boot NVS load; increments once per successful config or calibration mutation outside ACTIVE","context_epoch":"host: supplied only by SET CONTEXT and independent of config_epoch"},"evidence":"ACK hash must equal START_ACK and DEVICE_CONTEXT context_sha256; mismatch is typed fatal termination/no TAG1 fallback","max_base64url_bytes":1366,"max_set_context_command_bytes":1379,"max_utf8_bytes":1024,"optional_omit_when_unknown":["capture_session_id","recording_id","fault_injection_id"],"rejection":"#CONTEXT error code=command_rejected\\n","required":["schema","context_epoch","config_sha256","calibration_sha256"],"rules":"no_nulls; strings_NFC; hashes_lowercase_64_hex; u32_context_epoch_host_published_only; UTF-8_sorted_keys_compact_separators","schema":"oglo.device_context.v1","set_command":"INACTIVE-only exact ASCII SET CONTEXT \\n","writer":"single_TX_writer_when_implemented"},"schema":"oglo.diagnostics_contract.v1","scope":"phase_0_schema_and_vectors_only_no_runtime_behavior_changes","status":"implementation-contract-not-hardware-captured","tag1":{"crc":"none","header_fields":[{"constant":"a55a","endianness":"n/a","length":2,"name":"magic","offset":0,"semantic":"TAG1 magic A5 5A","wire_type":"bytes"},{"endianness":"little","length":1,"name":"type","offset":2,"semantic":"frame type","valid":"1 tactile|2 imu|3 mag","wire_type":"u8"},{"endianness":"little","length":2,"name":"payload_len","offset":3,"semantic":"payload bytes","valid":"120|12|6 by type","wire_type":"u16"},{"endianness":"little","length":4,"name":"seq","offset":5,"semantic":"per modality sequence","wire_type":"u32"},{"endianness":"little","length":4,"name":"timestamp_us","offset":9,"semantic":"per modality timestamp","wire_type":"u32"}],"header_format":"<2sBHII","header_len":13,"magic_hex":"a55a","physical_vectors":["spec/vectors/tag_tactile_133b.bin","spec/vectors/tag_imu_25b.bin","spec/vectors/tag_mag_19b.bin"],"sequence_scope":"per_frame_type","types":{"1":{"fields":[{"endianness":"little","formula":"a=b0<<4|b1>>4; b=(b1&0x0f)<<8|b2","length":120,"name":"taxels","offset":0,"semantic":"80 finger,row,col counts","wire_type":"packed_u12_be_pairs"}],"name":"tactile","payload_len":120},"2":{"fields":[{"endianness":"little","length":2,"name":"ax","offset":0,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"ay","offset":2,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"az","offset":4,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gx","offset":6,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gy","offset":8,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gz","offset":10,"semantic":"raw IMU axis","wire_type":"i16"}],"name":"imu","payload_len":12},"3":{"fields":[{"endianness":"little","length":2,"name":"mx","offset":0,"semantic":"raw magnetometer axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"my","offset":2,"semantic":"raw magnetometer axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"mz","offset":4,"semantic":"raw magnetometer axis","wire_type":"i16"}],"name":"mag","payload_len":6}}},"tag2":{"batch_flush_deadlines_ms":{"imu":8,"mag":32},"crc32":{"algorithm":"CRC-32/ISO-HDLC","check_hex":"cbf43926","coverage":"exact_header_plus_payload","init_hex":"ffffffff","reference":"zlib.crc32","reflected_polynomial_hex":"edb88320","xorout_hex":"ffffffff"},"device_time_us":"actual monotonic u64; data header is first sample time and sample=header+dt; per-modality reconstructed sample times strictly increase; global wire header times may move backward and are not causal order","header_fields":[{"constant":"a55b","endianness":"n/a","length":2,"name":"magic","offset":0,"semantic":"TAG2 magic A5 5B","wire_type":"bytes"},{"endianness":"little","invalid":"unknown type rejected","length":1,"name":"type","offset":2,"semantic":"frame type","wire_type":"u8"},{"endianness":"little","invalid":"0..512 and exact type layout","length":2,"name":"payload_len","offset":3,"semantic":"payload bytes only","wire_type":"u16"},{"endianness":"little","length":4,"name":"seq","offset":5,"rule":"one space per stream_id","semantic":"global admitted TX sequence","wire_type":"u32"},{"endianness":"little","length":8,"name":"device_time_us","offset":9,"semantic":"data first sample/control event time","wire_type":"u64"},{"endianness":"little","invalid":"zero rejected","length":8,"name":"stream_id","offset":17,"semantic":"nonzero random boot-local stream","wire_type":"u64"}],"header_format":"<2sBHIQQ","header_len":25,"idempotent_control_duplicates":"same-nonce START replay only in STARTING/ACTIVE; matching STOP retry replays cached STOP_ACK within the same boot; retired nonce never reopens","lifecycle_order":"START_ACK first; BOOT_SUMMARY then DEVICE_CONTEXT before data; requested STOP: producer boundary blocks new producer admission, drain accepted FIFO, STREAM_EVIDENCE, STOP_ACK last; fatal context/topology: producer boundary blocks new admission, drain accepted FIFO, STREAM_FAILURE, STREAM_EVIDENCE, no successful STOP_ACK; failure/evidence seq allocated after drained FIFO","magic_hex":"a55b","max_payload_len":512,"physical_rollout_gates":{"estimated_steady_wire_bps_max":55000,"normal_run_required_zero":["queue_drops","malformed_frames","crc_failures","wrong_stream_frames"],"queue_ram_cap":"must_freeze_before_phase5_approval","throughput_jitter_status":"unqualified_until_real_hardware"},"sequence_scope":"one_u32_space_per_stream_id_in_TX_queue_wire_admission_order; START_ACK_seq_0; modulo_2^32; atomic_sequence_allocation_and_queue_admission; failed_queue_admission_does_not_consume_seq","stream_id":"nonzero_random_u64_not_reused_within_one_mcu_boot","trailer_format":">4; b=(b1&0x0f)<<8|b2; 0..4095","length":120,"name":"taxels","offset":0,"semantic":"80 counts in finger,row,col order","wire_type":"packed_u12_be_pairs"}],"name":"tactile","payload_format":"packed12_80_taxels","payload_len":120},"128":{"fields":[{"constant":2,"endianness":"little","length":1,"name":"protocol_version","offset":0,"semantic":"must be 2","wire_type":"u8"},{"endianness":"n/a","length":16,"name":"nonce","offset":1,"semantic":"command nonce","wire_type":"bytes"},{"endianness":"n/a","invalid":"zero","length":16,"name":"mcu_boot_id","offset":17,"semantic":"random boot ID","wire_type":"bytes"},{"endianness":"n/a","length":32,"name":"application_sha256","offset":33,"semantic":"firmware image digest","wire_type":"bytes"},{"endianness":"little","length":4,"name":"config_epoch","offset":65,"semantic":"firmware config epoch","wire_type":"u32"},{"endianness":"n/a","length":32,"name":"context_sha256","offset":69,"semantic":"host canonical context digest","wire_type":"bytes"},{"endianness":"n/a","length":32,"name":"contract_sha256","offset":101,"semantic":"contract input hash","wire_type":"bytes"}],"name":"START_ACK","payload_format":"?x`Nj@(%KvL`>j6SCH-G>D literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/boot_summary.expected.json b/spec/diagnostics_v1/vectors/boot_summary.expected.json new file mode 100644 index 0000000..3f6c96d --- /dev/null +++ b/spec/diagnostics_v1/vectors/boot_summary.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590044592","expected":{"header":{"device_time_us":"8590044592","magic_hex":"a55b","payload_len":131,"seq":1,"stream_id":"72623859790382856","type":131},"payload":{"application_sha256":"1111111111111111111111111111111111111111111111111111111111111111","calibration_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","config_epoch":7,"config_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","firmware_schema":6,"firmware_semver_major":0,"firmware_semver_minor":9,"firmware_semver_patch":12,"hardware_model_id":1330072655,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","reset_reason":2,"reset_reason_raw":2864434397,"tag_ver_max":2}},"name":"boot_summary","payload_len":131,"seq":1,"stream_id":"72623859790382856","type":131} diff --git a/spec/diagnostics_v1/vectors/device_context.bin b/spec/diagnostics_v1/vectors/device_context.bin new file mode 100644 index 0000000000000000000000000000000000000000..c37c337843f6a347291bbb620b69360b9cd0d580 GIT binary patch literal 160 zcmZ1)-O|j!#K6FC;0hy{=3r-IWnpGw6i_sB4oNL*pRsb+={s-#@w*TJEF#r+F3GL= zyWlX-v${Vg3cek(*=zoHSF?eO!@cR#WIpZeXbR9UIJZBl`16zrMV00PHhV7yZGPb$ gD!F^x3j3UI>_9hw@GpihjI|5_42+D7>fxI;07PRtnE(I) literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/device_context.expected.json b/spec/diagnostics_v1/vectors/device_context.expected.json new file mode 100644 index 0000000..61bead9 --- /dev/null +++ b/spec/diagnostics_v1/vectors/device_context.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590054592","expected":{"header":{"device_time_us":"8590054592","magic_hex":"a55b","payload_len":131,"seq":2,"stream_id":"72623859790382856","type":132},"payload":{"calibration_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","config_epoch":7,"config_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","context_epoch":7,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","imu_axes_id":1,"imu_hz":500,"mag_axes_id":1,"mag_hz":125,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","tactile_hz":250,"tactile_packing_id":1,"taxel_count":80}},"name":"device_context","payload_len":131,"seq":2,"stream_id":"72623859790382856","type":132} diff --git a/spec/diagnostics_v1/vectors/imu.bin b/spec/diagnostics_v1/vectors/imu.bin new file mode 100644 index 0000000000000000000000000000000000000000..797928ec2c2fb8b42d63e0bea05e2b62935078b0 GIT binary patch literal 86 zcmZ1)&1A{I!oa}rK#7Tg2}pCWv$3);Gckh11c3O%5+D)t{{lOs03)2YfS*YKs2D0& PAkHkn4Cfttot_T>a9I-y literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/imu.expected.json b/spec/diagnostics_v1/vectors/imu.expected.json new file mode 100644 index 0000000..9955a7f --- /dev/null +++ b/spec/diagnostics_v1/vectors/imu.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590074592","expected":{"header":{"device_time_us":"8590074592","magic_hex":"a55b","payload_len":57,"seq":4,"stream_id":"72623859790382856","type":2},"payload":{"count":4,"records":[{"ax":4096,"ay":0,"az":-4096,"dt_us":0,"gx":164,"gy":0,"gz":-164},{"ax":4097,"ay":1,"az":-4096,"dt_us":2000,"gx":164,"gy":0,"gz":-164},{"ax":4098,"ay":2,"az":-4096,"dt_us":4000,"gx":164,"gy":0,"gz":-164},{"ax":4099,"ay":3,"az":-4096,"dt_us":6000,"gx":164,"gy":0,"gz":-164}]}},"name":"imu","payload_len":57,"seq":4,"stream_id":"72623859790382856","type":2} diff --git a/spec/diagnostics_v1/vectors/mag.bin b/spec/diagnostics_v1/vectors/mag.bin new file mode 100644 index 0000000000000000000000000000000000000000..6614e237ef6fe8e52542b6d1b185b18e44d1cc84 GIT binary patch literal 62 zcmZ1)&8*14%D}+z!IO!B2}pCWv$3);GcmF-Fzk|IU|9UgL4LOsBam*e+atvUqz}aI Nm0||cjK>Zq0|0QJ5M}@X literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/mag.expected.json b/spec/diagnostics_v1/vectors/mag.expected.json new file mode 100644 index 0000000..3641863 --- /dev/null +++ b/spec/diagnostics_v1/vectors/mag.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590084592","expected":{"header":{"device_time_us":"8590084592","magic_hex":"a55b","payload_len":33,"seq":5,"stream_id":"72623859790382856","type":3},"payload":{"count":4,"records":[{"dt_us":0,"mx":6842,"my":0,"mz":-3421},{"dt_us":8000,"mx":6843,"my":1,"mz":-3421},{"dt_us":16000,"mx":6844,"my":2,"mz":-3421},{"dt_us":24000,"mx":6845,"my":3,"mz":-3421}]}},"name":"mag","payload_len":33,"seq":5,"stream_id":"72623859790382856","type":3} diff --git a/spec/diagnostics_v1/vectors/start_ack.bin b/spec/diagnostics_v1/vectors/start_ack.bin new file mode 100644 index 0000000000000000000000000000000000000000..1802a162e7a477f6aae51c4cbbc69a8bcfebe672 GIT binary patch literal 162 zcmZ1)-O$Pa0t?z08JK`H2Rj=p3o{cV6N8|Vu}f%LdB@CEyU*Nx_g_HK$T=jntbNAH zU8nE7{l_mz0I&lMb0GlyZNIcm<+HecG|KH>#-C^RT3KIh`4{dZ+WOvwaraARx$fwk FTL43xFqQxS literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/start_ack.expected.json b/spec/diagnostics_v1/vectors/start_ack.expected.json new file mode 100644 index 0000000..55dbc8a --- /dev/null +++ b/spec/diagnostics_v1/vectors/start_ack.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590034592","expected":{"header":{"device_time_us":"8590034592","magic_hex":"a55b","payload_len":133,"seq":0,"stream_id":"72623859790382856","type":128},"payload":{"application_sha256":"1111111111111111111111111111111111111111111111111111111111111111","config_epoch":7,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","contract_sha256":"4f3df485950e38d7c55a46de68fce6de8505eab4fe574c1585ef4401bbe9031e","mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","nonce_hex":"00112233445566778899aabbccddeeff","protocol_version":2}},"name":"start_ack","payload_len":133,"seq":0,"stream_id":"72623859790382856","type":128} diff --git a/spec/diagnostics_v1/vectors/stop_ack.bin b/spec/diagnostics_v1/vectors/stop_ack.bin new file mode 100644 index 0000000000000000000000000000000000000000..7200c56fcb6c143eb3371be87f3e487dd1187c8c GIT binary patch literal 46 zcmZ1)-6+Vw!N9WhT($em-FN>PTtUByYK!BC>lA3q?WbM zSh?%;owxt^7ql^g)iV@A^nuJk#x4{Bpm7XrK+K4QL1HXO7#klX1_L0y3=GRTUK#@c D39u)= literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/stream_evidence.expected.json b/spec/diagnostics_v1/vectors/stream_evidence.expected.json new file mode 100644 index 0000000..329175c --- /dev/null +++ b/spec/diagnostics_v1/vectors/stream_evidence.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590104592","expected":{"header":{"device_time_us":"8590104592","magic_hex":"a55b","payload_len":202,"seq":7,"stream_id":"72623859790382856","type":133},"payload":{"counter_saturated":0,"deadline_misses":0,"end_config_epoch":7,"end_context_epoch":7,"end_context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","end_time_us":"8590094592","evidence_counts_as_transmitted":0,"first_admitted_seq":0,"imu_dropped_samples":0,"imu_enqueued_samples":4,"imu_produced_samples":4,"imu_transmitted_samples":4,"last_admitted_seq":6,"mag_dropped_samples":0,"mag_enqueued_samples":4,"mag_produced_samples":4,"mag_transmitted_samples":4,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","nonce_hex":"00112233445566778899aabbccddeeff","queue_drops":0,"reserved":0,"short_writes":0,"start_config_epoch":7,"start_context_epoch":7,"start_context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","start_time_us":"8590034592","stop_ack_counts_as_transmitted":0,"tactile_dropped_samples":0,"tactile_enqueued_samples":1,"tactile_produced_samples":1,"tactile_transmitted_samples":1,"terminal_reason":1}},"name":"stream_evidence","payload_len":202,"seq":7,"stream_id":"72623859790382856","type":133} diff --git a/spec/diagnostics_v1/vectors/stream_failure.bin b/spec/diagnostics_v1/vectors/stream_failure.bin new file mode 100644 index 0000000000000000000000000000000000000000..232160b67e55c3eccdc42673bd3ef9bd0962d33e GIT binary patch literal 68 qcmZ1)-K5UI#sCC`Obkpwii4evm4%s!k&%Ic6UcQT0P_3J+yDSa*bQ_5 literal 0 HcmV?d00001 diff --git a/spec/diagnostics_v1/vectors/stream_failure.expected.json b/spec/diagnostics_v1/vectors/stream_failure.expected.json new file mode 100644 index 0000000..55c9012 --- /dev/null +++ b/spec/diagnostics_v1/vectors/stream_failure.expected.json @@ -0,0 +1 @@ +{"device_time_us":"8590094592","expected":{"header":{"device_time_us":"8590094592","magic_hex":"a55b","payload_len":39,"seq":6,"stream_id":"72623859790382856","type":130},"payload":{"code":1,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","detail":9,"fatal":0}},"name":"stream_failure","payload_len":39,"seq":6,"stream_id":"72623859790382856","type":130} diff --git a/spec/diagnostics_v1/vectors/tactile.bin b/spec/diagnostics_v1/vectors/tactile.bin new file mode 100644 index 0000000000000000000000000000000000000000..dfc1cb6d7997eb310abba04a6b9c9c40c59336c8 GIT binary patch literal 149 zcmZ1)%~-*}%)r2K;Wr}#6OiU$XJchyW@40g{3@ULUB2 2**32 and stream != 0 + expected = json.loads((SPEC / "vectors" / f"{name}.expected.json").read_text()) + assert (kind, seq, str(clock), str(stream), plen) == (expected["type"], expected["seq"], expected["device_time_us"], expected["stream_id"], expected["payload_len"]) + seqs.append(seq) + if kind in (2, 3): + count = raw[25]; stride = 14 if kind == 2 else 8 + assert 1 <= count <= 8 and plen == 1 + stride * count + offsets = [struct.unpack_from("= {"header"} + assert expected["expected"].get("payload") or expected["expected"].get("payload_hex") + + +def test_canonical_context_profile_and_negative_examples(): + cases = json.loads((SPEC / "cases.json").read_text())["contexts"] + valid = cases["valid_canonical_json"] + assert gen.validate_context(valid)["schema"] == "oglo.device_context.v1" + token = cases["valid_set_context_base64url"] + assert "=" not in token and base64.urlsafe_b64decode(token + "==").decode() == valid + for name, raw in cases["invalid"].items(): + try: + if name in ("padded_base64url", "non_url_safe", "bad_utf8"): + gen.decode_context_token(raw) + else: + gen.validate_context(raw) + except ValueError: pass + else: raise AssertionError(raw) + for bad in ('{"x":null}', '{"x":1.0}', '{"e\\u0301":1}', '{"x":1,"x":2}'): + try: gen.canonical(gen.strict_json(bad)) + except ValueError: pass + else: raise AssertionError(bad) + + +def test_control_vectors_decode_every_named_field_and_epoch_owner(): + contract = json.loads((SPEC / "contract.json").read_text()) + expected = { + name: json.loads((SPEC / "vectors" / f"{name}.expected.json").read_text())["expected"]["payload"] + for name in ("start_ack", "stop_ack", "stream_failure", "boot_summary", "device_context", "stream_evidence") + } + boot = expected["boot_summary"] + assert (boot["firmware_semver_major"], boot["firmware_semver_minor"], boot["firmware_semver_patch"]) == (0, 9, 12) + assert boot["reset_reason"] == 2 and boot["config_epoch"] == 7 + assert len((SPEC / "vectors" / "boot_summary.bin").read_bytes()) == 25 + 131 + 4 + context = expected["device_context"] + assert (context["config_epoch"], context["context_epoch"]) == (7, 7) + assert (context["taxel_count"], context["tactile_packing_id"], context["imu_axes_id"], context["mag_axes_id"]) == (80, 1, 1, 1) + assert len((SPEC / "vectors" / "device_context.bin").read_bytes()) == 25 + 131 + 4 + evidence = expected["stream_evidence"] + for modality, total in (("tactile", 1), ("imu", 4), ("mag", 4)): + assert evidence[f"{modality}_produced_samples"] == total + assert evidence[f"{modality}_enqueued_samples"] == total + assert evidence[f"{modality}_transmitted_samples"] == total + assert evidence[f"{modality}_dropped_samples"] == 0 + assert evidence["queue_drops"] == evidence["short_writes"] == evidence["deadline_misses"] == 0 + assert evidence["evidence_counts_as_transmitted"] == evidence["stop_ack_counts_as_transmitted"] == 0 + assert evidence["reserved"] == 0 + assert contract["context"]["epoch_ownership"]["config_epoch"].startswith("firmware") + assert contract["context"]["epoch_ownership"]["context_epoch"].startswith("host") + for kind in (128, 129, 130, 131, 132, 133): + fields = contract["tag2"]["types"][str(kind)]["fields"] + assert all({"name", "offset", "wire_type", "length", "semantic"} <= set(field) for field in fields) + + +def test_executable_parser_admission_vectors_and_tail_preservation(): + cases = json.loads((SPEC / "cases.json").read_text()) + by_name = {case["name"]: case for case in cases["parser_cases"]} + for case in cases["parser_cases"]: + packets, remainder, malformed = gen.parse_tag2(bytes.fromhex(case["input_hex"])) + cached = () + if case["name"] == "cached_start_ack": + packet = packets[0] + cached = ((packet["type"], packet["seq"], packet["payload"]),) + state = gen.admit(packets, gen.STREAM, case["initial_expected_seq"], cached) + assert remainder.hex() == case["expected_remainder_hex"] + assert malformed == case["expected_malformed"] + assert state == case["expected_admission"] + assert by_name["crc_data_reject_continue"]["expected_malformed"] == 1 + assert by_name["crc_start_ack_malformed"]["expected_malformed"] == 1 + assert by_name["wrong_stream_stale"]["expected_admission"]["stale"] == 1 + assert by_name["wrong_stream_stale"]["expected_admission"]["expected_seq"] == 0 + assert by_name["forward_gap"]["expected_admission"]["gaps"] == 1 + assert by_name["duplicate"]["expected_admission"]["duplicates"] == 1 + assert by_name["wrap"]["expected_admission"]["expected_seq"] == 1 + assert by_name["cached_start_ack"]["expected_admission"]["control_replays"] == 1 + partial = by_name["coalesced_start_partial_boot"] + assert len(partial["expected_packets"]) == 1 and partial["expected_remainder_hex"] + assert all(case["expected_admission"]["no_tag1_fallback"] for case in cases["parser_cases"]) + + +def test_exact_command_grammar_nonce_lifecycle_and_fatal_order(): + cases = json.loads((SPEC / "cases.json").read_text())["commands"] + assert [gen.validate_command(bytes.fromhex(raw))[0] for raw in cases["valid_hex"]] == ["start", "stop", "context"] + for raw in cases["invalid_hex"]: + try: + gen.validate_command(bytes.fromhex(raw)) + except ValueError: + pass + else: + raise AssertionError(raw) + transitions = {item["name"]: item for item in cases["transitions"]} + for item in transitions.values(): + active = item["active_nonce"] or None + actual = gen.command_transition(item["lifecycle"], bytes.fromhex(item["command_hex"]), active_nonce=active, retired_nonces=tuple(item["retired_nonces"])) + assert actual == item["expected"] + assert transitions["same_start_active"]["expected"]["response"] == "cached_start_ack" + assert transitions["retired_start"]["expected"]["response"] == "nonce_retired" + assert transitions["retired_start"]["expected"]["reopen"] is False + assert transitions["different_start_active"]["expected"]["accepted"] is False + assert cases["fatal_order"] == ["producer_boundary", "drain_accepted_frames", "STREAM_FAILURE", "STREAM_EVIDENCE"] + assert cases["fatal_stop_ack"] == "forbidden" + + +def test_context_byte_limits_hash_preimages_and_physical_gates(): + cases = json.loads((SPEC / "cases.json").read_text())["contexts"] + contract = json.loads((SPEC / "contract.json").read_text()) + maximum = cases["max_valid_canonical_json"] + assert len(maximum.encode()) == gen.CONTEXT_MAX_UTF8 == cases["max_valid_utf8_bytes"] + assert cases["max_valid_token_bytes"] == 1366 + assert cases["max_valid_command_bytes"] == gen.CONTEXT_COMMAND_MAX == 1379 + gen.validate_context(maximum) + try: + gen.validate_context(cases["oversize_canonical_json"]) + except ValueError: + pass + else: + raise AssertionError("1025-byte context accepted") + assert hashlib.sha256(cases["valid_canonical_json"].encode()).hexdigest() == cases["context_sha256"] + assert contract["context"]["config_sha256"]["view"]["additional_fields"] is False + assert contract["context"]["calibration_sha256"]["view"]["fields"]["tactile_baseline_u16"] == "array[80]" + for name in ("config", "calibration"): + golden = cases[f"{name}_hash_golden"] + domain = bytes.fromhex(golden["domain_separator_hex"]) + view = golden["canonical_view_utf8"].encode("utf-8") + assert domain.endswith(b"\0") + assert (domain + view).hex() == golden["preimage_hex"] + assert hashlib.sha256(domain + view).hexdigest() == golden["sha256"] + assert contract["context"][f"{name}_sha256"]["golden_sha256"] == golden["sha256"] + gates = contract["tag2"]["physical_rollout_gates"] + assert gates["estimated_steady_wire_bps_max"] == 55000 + assert gates["throughput_jitter_status"] == "unqualified_until_real_hardware" + assert gates["queue_ram_cap"] == "must_freeze_before_phase5_approval" + + +def test_default_batch_timing_and_per_modality_clock_rule(): + cases = json.loads((SPEC / "cases.json").read_text())["timing"] + contract = json.loads((SPEC / "contract.json").read_text())["tag2"] + assert cases["imu_default_offsets_us"] == [0, 2000, 4000, 6000] + assert cases["mag_default_offsets_us"] == [0, 8000, 16000, 24000] + assert (cases["imu_flush_deadline_ms"], cases["mag_flush_deadline_ms"]) == (8, 32) + assert cases["global_wire_header_monotonic_required"] is False + assert cases["per_modality_reconstructed_sample_monotonic_required"] is True + assert contract["batch_flush_deadlines_ms"] == {"imu": 8, "mag": 32} + + +def test_start_ack_negotiation_is_derived_from_parsed_bytes(): + cases = json.loads((SPEC / "cases.json").read_text())["negotiation_cases"] + outcomes = {} + for case in cases: + packets, remainder, malformed = gen.parse_tag2(bytes.fromhex(case["input_hex"])) + actual = gen.admit_start_ack( + packets, + expected_nonce=gen.NONCE, + expected_contract_sha256=bytes.fromhex("55" * 32), + expected_context_sha256=gen.CONTEXT, + lifecycle=case["lifecycle"], + ) + assert actual == case["derived"] + outcomes[case["name"]] = actual + assert outcomes["start_positive"]["accepted"] is True + for name, reason in (("start_wrong_nonce", "wrong_nonce"), ("start_wrong_version", "wrong_protocol_version"), ("start_wrong_contract", "wrong_contract_sha256"), ("start_wrong_context", "wrong_context_sha256"), ("start_zero_boot", "zero_boot_id"), ("start_zero_stream", "zero_stream_id"), ("start_wrong_first_type", "no_valid_start_ack"), ("start_wrong_lifecycle", "wrong_lifecycle"), ("start_crc", "no_valid_start_ack")): + value = outcomes[name] + assert not value["accepted"] and value["fail_closed"] and value["no_tag1_fallback"] + assert value["reason"] == reason diff --git a/tools/generate_diagnostics_contract.py b/tools/generate_diagnostics_contract.py new file mode 100644 index 0000000..4703260 --- /dev/null +++ b/tools/generate_diagnostics_contract.py @@ -0,0 +1,522 @@ +#!/usr/bin/env python3 +"""Generate the standalone Phase-0 diagnostics contract and conformance bytes.""" +from __future__ import annotations + +import argparse +import base64 +import hashlib +import json +import re +import struct +import unicodedata +import zlib +from pathlib import Path +from typing import Any + +ROOT = Path(__file__).resolve().parents[1] +OUT = ROOT / "spec" / "diagnostics_v1" +VECTORS = OUT / "vectors" +PREFIX = b"OGLO-DIAGNOSTICS-CONTRACT-V1\0" +HEADER = struct.Struct("<2sBHIQQ") +CRC = struct.Struct(" Any: + if "\r" in raw: + raise ValueError("canonical JSON uses LF only") + return json.loads(raw, object_pairs_hook=_pairs, parse_constant=lambda x: (_ for _ in ()).throw(ValueError(f"invalid JSON constant {x}"))) + + +def canonical(value: Any) -> bytes: + def norm(v: Any) -> Any: + if v is None: + raise ValueError("null is not allowed in canonical contract/context JSON") + if isinstance(v, float): + raise ValueError("floats are not allowed in canonical contract/context JSON") + if isinstance(v, str): + if unicodedata.normalize("NFC", v) != v: + raise ValueError("strings must already be NFC") + return v + if isinstance(v, list): + return [norm(x) for x in v] + if isinstance(v, dict): + result = {} + for k, x in v.items(): + if not isinstance(k, str) or unicodedata.normalize("NFC", k) != k or k in result: + raise ValueError("keys must be unique NFC strings") + result[k] = norm(x) + return result + return v + return json.dumps(norm(value), sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode() + + +def config_calibration_golden() -> tuple[bytes, bytes, bytes, bytes]: + """Closed canonical preimages; never a native/C struct image or padding.""" + config_view={"schema":"oglo.config_view.v1","hardware_model_id":MODEL,"firmware_schema":6,"firmware_semver":{"major":0,"minor":9,"patch":12},"tag_ver_max":2,"tactile":{"taxel_count":80,"order":"finger,row,col","packing":"packed12","rate_hz":250},"imu":{"axes":["ax","ay","az","gx","gy","gz"],"raw":"i16le","rate_hz":500},"mag":{"axes":["mx","my","mz"],"raw":"i16le","rate_hz":125}} + cal_view={"schema":"oglo.calibration_view.v1","tactile_baseline_u16":[0]*80,"tactile_threshold_u16":[0]*80,"imu_bias_i16":[0]*6,"mag_bias_i16":[0]*3} + cb=canonical(config_view); kb=canonical(cal_view) + return cb,kb,hashlib.sha256(b"OGLO-CONFIG-V1\0"+cb).digest(),hashlib.sha256(b"OGLO-CALIBRATION-V1\0"+kb).digest() + + +def validate_context(raw: str) -> dict[str, Any]: + if len(raw.encode("utf-8")) > CONTEXT_MAX_UTF8: + raise ValueError("context exceeds UTF-8 byte limit") + value = strict_json(raw) + if not isinstance(value, dict) or canonical(value).decode() != raw: + raise ValueError("context must be compact sorted canonical JSON") + required = {"schema", "context_epoch", "config_sha256", "calibration_sha256"} + optional = {"capture_session_id", "recording_id", "fault_injection_id"} + if set(value) - required - optional or not required <= set(value): + raise ValueError("context has missing or unknown keys") + if value["schema"] != "oglo.device_context.v1": + raise ValueError("context schema must be oglo.device_context.v1") + if type(value["context_epoch"]) is not int or not 0 <= value["context_epoch"] <= 0xffffffff: + raise ValueError("context_epoch must be u32") + for key in ("config_sha256", "calibration_sha256"): + x = value[key] + if not isinstance(x, str) or len(x) != 64 or any(c not in "0123456789abcdef" for c in x): + raise ValueError(f"{key} must be lowercase SHA-256 hex") + if any(not isinstance(value[k], str) or not value[k] for k in optional & set(value)): + raise ValueError("context optional identifiers must be nonempty strings when present") + return value + + +def decode_context_token(token: str) -> dict[str, Any]: + """Decode the exact unpadded base64url SET CONTEXT argument.""" + if not isinstance(token, str) or not token or "=" in token or len("SET CONTEXT ".encode() + token.encode() + b"\n") > CONTEXT_COMMAND_MAX: + raise ValueError("context token must be unpadded base64url") + if any(c not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" for c in token): + raise ValueError("context token has a non-base64url character") + try: + raw = base64.urlsafe_b64decode(token + "=" * (-len(token) % 4)) + text = raw.decode("utf-8") + except (ValueError, UnicodeDecodeError) as exc: + raise ValueError("context token is not UTF-8 base64url") from exc + if base64.urlsafe_b64encode(raw).decode().rstrip("=") != token: + raise ValueError("context token is not canonical base64url") + return validate_context(text) + + +def validate_command(command: bytes) -> tuple[str, str]: + """Validate the exact LF-terminated, case-sensitive TAG2 command grammar.""" + if not isinstance(command, bytes) or not COMMAND_RE.fullmatch(command): + raise ValueError("command must be exact ASCII with one LF and no extra tokens") + text = command[:-1].decode("ascii") + if text.startswith("STREAM TAG2 ON nonce="): + return "start", text.rsplit("=", 1)[1] + if text.startswith("STREAM TAG2 OFF nonce="): + return "stop", text.rsplit("=", 1)[1] + token = text.removeprefix("SET CONTEXT ") + decode_context_token(token) + return "context", token + + +def command_transition( + lifecycle: str, + command: bytes, + *, + active_nonce: str | None = None, + retired_nonces: tuple[str, ...] = (), +) -> dict[str, Any]: + """Normative command admission model used by cross-language vectors.""" + kind, value = validate_command(command) + result = { + "lifecycle": lifecycle, + "accepted": False, + "response": "command_rejected", + "reopen": False, + } + if active_nonce is not None: + result["active_nonce"] = active_nonce + if kind == "context": + if lifecycle == "IDLE": + result.update(accepted=True, response="context_ack") + return result + if kind == "start": + if lifecycle in {"STARTING", "ACTIVE"} and value == active_nonce: + result.update(accepted=True, response="cached_start_ack") + elif lifecycle == "IDLE" and value not in retired_nonces: + result.update(lifecycle="STARTING", active_nonce=value, accepted=True, response="new_start") + elif value in retired_nonces: + result["response"] = "nonce_retired" + else: + result["response"] = "nonce_conflict" + return result + if lifecycle in {"ACTIVE", "STOPPING"} and value == active_nonce: + result.update(lifecycle="STOPPING", accepted=True, response="stop_or_cached_stop_ack") + elif value in retired_nonces: + result.update(accepted=True, response="cached_stop_ack") + else: + result["response"] = "nonce_conflict" + return result + + +def _declared_len_possible(kind: int, length: int) -> bool: + fixed = {1: 120, 128: 133, 129: 17, 130: 39, 131: 131, 132: 131, 133: 202} + if kind in fixed: + return length == fixed[kind] + if kind == 2: + return length in {1 + 14 * count for count in range(1, 9)} + if kind == 3: + return length in {1 + 8 * count for count in range(1, 9)} + return False + + +def _payload_shape_valid(kind: int, payload: bytes) -> bool: + if not _declared_len_possible(kind, len(payload)): + return False + if kind not in (2, 3): + return True + stride = 14 if kind == 2 else 8 + count = payload[0] + if not 1 <= count <= 8 or len(payload) != 1 + count * stride: + return False + offsets = [struct.unpack_from("512 or not _declared_len_possible(typ, plen) or end>len(buf): + if end>len(buf) and _declared_len_possible(typ, plen): return packets,buf[j:],bad + bad+=1;i=j+1;continue + raw=buf[j:end] + if struct.unpack_from(" str: return hashlib.sha256(data).hexdigest() +def entry(path: Path, data: bytes, provenance: str, status: str) -> dict[str, Any]: + return {"name": path.name, "owner": "oglo-python", "path": path.relative_to(ROOT).as_posix(), "provenance": provenance, "status": status, "size": len(data), "sha256": sha(data)} + + +def frame(kind: int, payload: bytes, seq: int, clock: int, stream: int) -> bytes: + head = HEADER.pack(MAGIC, kind, len(payload), seq, clock, stream) + return head + payload + CRC.pack(zlib.crc32(head + payload)) + + +def tactile() -> bytes: + values = range(500, 580); out = bytearray() + for a, b in zip(values[::2], values[1::2]): out += bytes((a >> 4, ((a & 15) << 4) | (b >> 8), b & 255)) + return bytes(out) + + +def imu_batch() -> bytes: + return bytes([4]) + b"".join(struct.pack(" bytes: + return bytes([4]) + b"".join(struct.pack(" dict[str, Any]: + config_bytes, calibration_bytes, config_hash, calibration_hash = config_calibration_golden() + f = lambda name, offset, wire_type, length, semantic, **rules: {"name": name, "offset": offset, "wire_type": wire_type, "endianness": "little" if wire_type not in ("bytes", "ascii") else "n/a", "length": length, "semantic": semantic, **rules} + header_fields = [f("magic",0,"bytes",2,"TAG2 magic A5 5B",constant="a55b"),f("type",2,"u8",1,"frame type",invalid="unknown type rejected"),f("payload_len",3,"u16",2,"payload bytes only",invalid="0..512 and exact type layout"),f("seq",5,"u32",4,"global admitted TX sequence",rule="one space per stream_id"),f("device_time_us",9,"u64",8,"data first sample/control event time"),f("stream_id",17,"u64",8,"nonzero random boot-local stream",invalid="zero rejected")] + raw_imu = [f("dt_us",0,"u16",2,"offset from header time",rule="relative to record base; first=0, later strictly increasing"), *[f(n,2+i*2,"i16",2,"raw IMU axis; relative to record base") for i,n in enumerate(("ax","ay","az","gx","gy","gz"))]] + raw_mag = [f("dt_us",0,"u16",2,"offset from header time",rule="relative to record base; first=0, later strictly increasing"), *[f(n,2+i*2,"i16",2,"raw magnetometer axis; relative to record base") for i,n in enumerate(("mx","my","mz"))]] + types = { + "1": {"name": "tactile", "payload_len": 120, "payload_format": "packed12_80_taxels", "fields": [f("taxels",0,"packed_u12_be_pairs",120,"80 counts in finger,row,col order",formula="a=b0<<4|b1>>4; b=(b1&0x0f)<<8|b2; 0..4095") ]}, + "2": {"name": "imu", "payload_format": "count:u8 + count*(dt_us:u16 + raw:<6h)", "count_range": [1, 8], "payload_len_formula": "1+14*count", "first_dt_us": 0, "later_dt_us": "strictly_increasing", "default_batch_target": 4, "fields": [f("count",0,"u8",1,"sample count",valid="1..8"), {"name":"records","offset":1,"wire_type":"array","endianness":"little","length":"14*count","stride":14,"element_offset_base":"relative_to_each_record","semantic":"IMU samples ax,ay,az,gx,gy,gz","element_fields":raw_imu}]}, + "3": {"name": "mag", "payload_format": "count:u8 + count*(dt_us:u16 + raw:<3h)", "count_range": [1, 8], "payload_len_formula": "1+8*count", "first_dt_us": 0, "later_dt_us": "strictly_increasing", "default_batch_target": 4, "fields": [f("count",0,"u8",1,"sample count",valid="1..8"), {"name":"records","offset":1,"wire_type":"array","endianness":"little","length":"8*count","stride":8,"element_offset_base":"relative_to_each_record","semantic":"magnetometer samples mx,my,mz","element_fields":raw_mag}]}, + "128": {"name": "START_ACK", "payload_format": "=2 selects min(host_max,device_max); malformed_or_contradictory_fails_closed", "command_encoding":"strict ASCII, exact uppercase and single spaces, one LF; CRLF/leading/trailing/extra tokens rejected", "commands": {"start": "STREAM TAG2 ON nonce=<32 lowercase hex>\n", "stop": "STREAM TAG2 OFF nonce=\n", "context":"SET CONTEXT \n"}, "start": "first valid frame after ON is START_ACK matching nonce, stream_id, contract_sha256; preserve fragment/coalesced tail; invalid ACK/CRC/wrong stream never downgrades", "active": "same START nonce retry returns byte-identical cached ACK/current stream; different nonce gives nonfatal nonce_conflict/no transition; only matching STOP accepted; other commands/context mutation nonfatal command_rejected; context/topology mutation uses fatal terminal order", "stop": "same STOP retry returns cached STOP_ACK within boot; retired START nonce cannot reopen; new nonce after terminal gets new stream_id", "host": "CRC-valid wrong-stream frame is stale evidence: discard and continue without changing current expected seq/time/counters; never TAG1 fallback", "receiver_sequence": "expected seq modulo 2^32; exact accepts; forward distance <2^31 records gap then accepts; backward distance is duplicate/out-of-order; cached control replay neither advances nor gaps", "parser_resync": "scan negotiated magic only; retain incomplete frame exactly and one possible magic-prefix byte when no complete magic; invalid type/length/CRC advances scan by one byte after the first magic byte; never reinterpret TAG2 as TAG1"}, + "context": {"schema": "oglo.device_context.v1", "required": ["schema", "context_epoch", "config_sha256", "calibration_sha256"], "optional_omit_when_unknown": ["capture_session_id", "recording_id", "fault_injection_id"], "max_utf8_bytes": CONTEXT_MAX_UTF8, "max_base64url_bytes": 1366, "max_set_context_command_bytes": CONTEXT_COMMAND_MAX, "rules": "no_nulls; strings_NFC; hashes_lowercase_64_hex; u32_context_epoch_host_published_only; UTF-8_sorted_keys_compact_separators", "context_sha256": {"algorithm":"SHA256","preimage":"canonical UTF-8 bytes without LF"}, "config_sha256": {"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":b"OGLO-CONFIG-V1\0".hex(),"golden_sha256":config_hash.hex(),"view":{"schema":"oglo.config_view.v1","additional_fields":False,"fields":{"hardware_model_id":"u32","firmware_schema":"u8","firmware_semver":"{major:u8,minor:u8,patch:u8}","tag_ver_max":"u8","tactile":{"taxel_count":"80","order":"finger,row,col","packing":"packed12","rate_hz":"u16"},"imu":{"axes":"array[ax,ay,az,gx,gy,gz]","raw":"i16le","rate_hz":"u16"},"mag":{"axes":"array[mx,my,mz]","raw":"i16le","rate_hz":"u16"}}}}, "calibration_sha256": {"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":b"OGLO-CALIBRATION-V1\0".hex(),"golden_sha256":calibration_hash.hex(),"view":{"schema":"oglo.calibration_view.v1","additional_fields":False,"fields":{"tactile_baseline_u16":"array[80]","tactile_threshold_u16":"array[80]","imu_bias_i16":"array[6]","mag_bias_i16":"array[3]"}}}, "epoch_ownership":{"config_epoch":"firmware: starts 1 after boot NVS load; increments once per successful config or calibration mutation outside ACTIVE","context_epoch":"host: supplied only by SET CONTEXT and independent of config_epoch"}, "set_command": "INACTIVE-only exact ASCII SET CONTEXT \\n", "ack": "#CONTEXT ok sha256=<64 lowercase hex>\\n", "rejection": "#CONTEXT error code=command_rejected\\n", "writer": "single_TX_writer_when_implemented", "active": "ACTIVE rejects mutation through typed STREAM_FAILURE command_rejected", "evidence": "ACK hash must equal START_ACK and DEVICE_CONTEXT context_sha256; mismatch is typed fatal termination/no TAG1 fallback"}, + "canonicalization": {"json": "UTF-8,NFC,LF,object_keys_sorted,compact_separators,no_duplicate_keys,no_null,no_NaN,no_float,unknown_optional_omitted; runtime_u64_and_ns_use_decimal_strings_when_JSON", "hash_preimage": "SHA256(b'OGLO-DIAGNOSTICS-CONTRACT-V1\\0' + canonical_json(manifest.contract_inputs))", "generated_vector_hashes": "inventory only; excluded from contract_sha256 preimage to avoid START_ACK self-reference"}} + + +def cases() -> dict[str, Any]: + config_bytes, calibration_bytes, config_hash, calibration_hash = config_calibration_golden() + valid = {"calibration_sha256": "3" * 64, "config_sha256": "2" * 64, "context_epoch": 7, "schema": "oglo.device_context.v1"} + raw = canonical(valid).decode() + token = base64.urlsafe_b64encode(raw.encode()).decode().rstrip("=") + max_value = {**valid, "recording_id": ""} + padding = CONTEXT_MAX_UTF8 - len(canonical(max_value)) + max_value["recording_id"] = "x" * padding + max_raw = canonical(max_value).decode() + oversize_value = {**max_value, "recording_id": max_value["recording_id"] + "x"} + oversize_raw = canonical(oversize_value).decode() + start_payload = struct.pack(" dict[str, Any]: + packets, remainder, malformed = parse_tag2(data) + state = admit(packets, STREAM, expected_seq, cached) + return { + "name": name, + "input_hex": data.hex(), + "initial_expected_seq": expected_seq, + "expected_packets": [ + {"type": packet["type"], "seq": packet["seq"], "stream_id": str(packet["stream_id"])} + for packet in packets + ], + "expected_remainder_hex": remainder.hex(), + "expected_malformed": malformed, + "expected_admission": state, + } + + corrupted_data = bytearray(tactile_frame) + corrupted_data[-1] ^= 1 + corrupted_ack = bytearray(start) + corrupted_ack[-1] ^= 1 + wrong_stream = frame(1, tactile(), 0, (2 << 32) + 4, OTHER_STREAM) + unknown = frame(99, b"", 0, (2 << 32) + 5, STREAM) + bad_length = frame(1, b"\0", 0, (2 << 32) + 6, STREAM) + sequence_cases = [ + parser_case("normal", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 1, 11, STREAM)), + parser_case("forward_gap", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 2, 12, STREAM)), + parser_case("duplicate", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 0, 10, STREAM)), + parser_case("out_of_order", frame(1, tactile(), 2, 10, STREAM), expected_seq=3), + parser_case("wrap", frame(1, tactile(), 0xFFFFFFFE, 10, STREAM) + frame(1, tactile(), 0xFFFFFFFF, 11, STREAM) + frame(1, tactile(), 0, 12, STREAM), expected_seq=0xFFFFFFFE), + parser_case("cached_start_ack", start, expected_seq=1, cached=((128, 0, start_payload),)), + ] + parser_cases = [ + parser_case("valid_data", tactile_frame, expected_seq=2), + parser_case("crc_data_reject_continue", bytes(corrupted_data), expected_seq=2), + parser_case("crc_start_ack_malformed", bytes(corrupted_ack)), + parser_case("wrong_stream_stale", wrong_stream), + parser_case("unknown_type", unknown), + parser_case("bad_type_length", bad_length), + parser_case("coalesced_start_boot", start + boot), + parser_case("coalesced_start_partial_boot", start + boot[:40]), + *[parser_case(f"split_start_{length}", start[:length]) for length in (1, 24, 25, 100)], + *sequence_cases, + ] + nonce_hex = NONCE.hex() + other_nonce = bytes.fromhex("ffeeddccbbaa99887766554433221100").hex() + valid_commands = [ + f"STREAM TAG2 ON nonce={nonce_hex}\n".encode(), + f"STREAM TAG2 OFF nonce={nonce_hex}\n".encode(), + f"SET CONTEXT {token}\n".encode(), + ] + transition_inputs = [ + ("new_start", "IDLE", valid_commands[0], None, ()), + ("same_start_active", "ACTIVE", valid_commands[0], nonce_hex, ()), + ("different_start_active", "ACTIVE", f"STREAM TAG2 ON nonce={other_nonce}\n".encode(), nonce_hex, ()), + ("matching_stop", "ACTIVE", valid_commands[1], nonce_hex, ()), + ("retired_start", "IDLE", valid_commands[0], None, (nonce_hex,)), + ("cached_stop", "IDLE", valid_commands[1], None, (nonce_hex,)), + ("context_idle", "IDLE", valid_commands[2], None, ()), + ("context_active", "ACTIVE", valid_commands[2], nonce_hex, ()), + ] + invalid_commands = [ + valid_commands[0][:-1], + valid_commands[0][:-1] + b"\r\n", + valid_commands[0].replace(b"STREAM", b"stream"), + valid_commands[0].replace(b"TAG2 ON", b"TAG2 ON"), + valid_commands[0].replace(nonce_hex.encode(), nonce_hex.upper().encode()), + valid_commands[0][:-1] + b" extra\n", + ] + def start_case(name, wire, lifecycle="STARTING"): + packets, tail, malformed = parse_tag2(wire) + return {"name":name,"input_hex":wire.hex(),"lifecycle":lifecycle,"malformed":malformed,"remainder_hex":tail.hex(),"derived":admit_start_ack(packets,expected_nonce=NONCE,expected_contract_sha256=bytes.fromhex("55"*32),expected_context_sha256=CONTEXT,lifecycle=lifecycle)} + def ack_variant(*, version=2, nonce=NONCE, boot_id=BOOT, contract=bytes.fromhex("55"*32), context=CONTEXT, stream=STREAM, kind=128): + payload=struct.pack(">4),((payload[j+1]&15)<<8)|payload[j+2]] + base["payload"]={"taxels":vals,"order":"finger,row,col"} + elif kind in (2,3): + stride=14 if kind==2 else 8; names=("ax","ay","az","gx","gy","gz") if kind==2 else ("mx","my","mz") + records=[] + for j in range(payload[0]): + values=struct.unpack_from(" dict[Path, bytes]: + c = contract(); cs = cases(); result = {OUT/"contract.json": canonical(c)+b"\n", OUT/"cases.json": canonical(cs)+b"\n"} + physical = [ROOT/"spec/vectors/tag_tactile_133b.bin", ROOT/"spec/vectors/tag_imu_25b.bin", ROOT/"spec/vectors/tag_mag_19b.bin"] + contract_entries = [entry(OUT/"contract.json",result[OUT/"contract.json"],"synthetic","implementation-contract-not-hardware-captured")] + [entry(p,p.read_bytes(),"physical_capture","existing_TAG1_capture") for p in physical] + contract_entries.sort(key=lambda x:x["name"]) + cases_entry = entry(OUT/"cases.json",result[OUT/"cases.json"],"synthetic","implementation-contract-not-hardware-captured") + inputs = {"schema":"oglo.diagnostics_manifest.v1","status":"implementation-contract-not-hardware-captured","canonicalization":c["canonicalization"],"contract_entries":contract_entries,"cases_entry":cases_entry} + h = sha(PREFIX+canonical(inputs)); + for r in records(bytes.fromhex(h)): + result[VECTORS/(r["name"]+".bin")] = r["frame"] + result[VECTORS/(r["name"]+".expected.json")] = canonical({k:v for k,v in r.items() if k != "frame"})+b"\n" + generated = [entry(p,d,"synthetic","implementation-contract-not-hardware-captured") for p,d in result.items() if p.parent == VECTORS] + generated.sort(key=lambda x:x["name"]) + result[OUT/"manifest.json"] = canonical({"contract_inputs":inputs,"contract_sha256":h,"vector_set_sha256":sha(canonical(generated)),"generated_vector_entries":generated,"conformance_pins":"Python/C++/TypeScript pin both contract_sha256 and vector_set_sha256"})+b"\n" + return result + + +def main() -> int: + check = argparse.ArgumentParser(); check.add_argument("--check",action="store_true"); args=check.parse_args(); wanted=files() + stale = sorted(p for p in VECTORS.glob("*") if p.is_file() and p not in wanted) + bad = [p for p,d in wanted.items() if not p.exists() or p.read_bytes()!=d] + stale + if args.check: + if bad: print("out of date: "+", ".join(p.relative_to(ROOT).as_posix() for p in bad)); return 1 + print(f"verified {len(wanted)} diagnostics contract artifacts"); return 0 + for p,d in wanted.items(): p.parent.mkdir(parents=True,exist_ok=True); p.write_bytes(d) + for p in stale: p.unlink() + print(f"generated {len(wanted)} diagnostics contract artifacts"); return 0 + +if __name__ == "__main__": raise SystemExit(main()) From 7d3514615d443c15dcd1fd1bc7eb2ddde2b2f4a6 Mon Sep 17 00:00:00 2001 From: Sungman Cho Date: Fri, 14 Aug 2026 20:01:13 +0900 Subject: [PATCH 2/3] test: reject competing TAG2 contracts --- spec/diagnostics_v1/README.md | 6 ++++++ tests/test_diagnostics_contract.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/spec/diagnostics_v1/README.md b/spec/diagnostics_v1/README.md index fc47c49..7e6d5a9 100644 --- a/spec/diagnostics_v1/README.md +++ b/spec/diagnostics_v1/README.md @@ -17,3 +17,9 @@ throughput PASS: real hardware jitter and throughput capture remain required. Regenerate with `python tools/generate_diagnostics_contract.py`; verify without writing with `python tools/generate_diagnostics_contract.py --check`. + +This directory is the only TAG2 protocol source of truth. Any SDK-facing +projection such as `spec/TAG_V2.json` must pin this manifest's +`contract_sha256` and `vector_set_sha256` and reproduce its header, sequence, +and typed-binary negotiation fields. An independent or ASCII-ACK TAG2 contract +is a conformance failure. diff --git a/tests/test_diagnostics_contract.py b/tests/test_diagnostics_contract.py index 7c6cf87..d2440fc 100644 --- a/tests/test_diagnostics_contract.py +++ b/tests/test_diagnostics_contract.py @@ -240,3 +240,22 @@ def test_start_ack_negotiation_is_derived_from_parsed_bytes(): value = outcomes[name] assert not value["accepted"] and value["fail_closed"] and value["no_tag1_fallback"] assert value["reason"] == reason + + +def test_competing_tag2_projection_pins_canonical_contract(): + """A stacked SDK branch must not introduce a second TAG2 source of truth.""" + projection_path = ROOT / "spec" / "TAG_V2.json" + if not projection_path.exists(): + return + projection = json.loads(projection_path.read_text()) + manifest = json.loads((SPEC / "manifest.json").read_text()) + contract = json.loads((SPEC / "contract.json").read_text()) + assert projection["contract_sha256"] == manifest["contract_sha256"] + assert projection["vector_set_sha256"] == manifest["vector_set_sha256"] + assert projection["frame"]["magic_hex"] == contract["tag2"]["magic_hex"] + assert projection["frame"]["header_format"] == contract["tag2"]["header_format"] + assert projection["frame"]["header_len"] == contract["tag2"]["header_len"] + assert projection["sequence_scope"] == contract["tag2"]["sequence_scope"] + assert projection["negotiation"]["start_command"] == contract["admission"]["commands"]["start"] + assert projection["negotiation"]["start_ack_encoding"] == "typed_binary_crc" + assert "start_ack_prefix" not in projection["negotiation"] From 55f8b8212efcdf5491dfc9573e6a7244f85bc8c5 Mon Sep 17 00:00:00 2001 From: Sungman Cho Date: Sat, 15 Aug 2026 00:09:15 +0900 Subject: [PATCH 3/3] revert: keep OGLO wire schema unchanged --- spec/diagnostics_v1/README.md | 25 - spec/diagnostics_v1/cases.json | 1 - spec/diagnostics_v1/contract.json | 1 - spec/diagnostics_v1/manifest.json | 1 - spec/diagnostics_v1/vectors/boot_summary.bin | Bin 160 -> 0 bytes .../vectors/boot_summary.expected.json | 1 - .../diagnostics_v1/vectors/device_context.bin | Bin 160 -> 0 bytes .../vectors/device_context.expected.json | 1 - spec/diagnostics_v1/vectors/imu.bin | Bin 86 -> 0 bytes spec/diagnostics_v1/vectors/imu.expected.json | 1 - spec/diagnostics_v1/vectors/mag.bin | Bin 62 -> 0 bytes spec/diagnostics_v1/vectors/mag.expected.json | 1 - spec/diagnostics_v1/vectors/start_ack.bin | Bin 162 -> 0 bytes .../vectors/start_ack.expected.json | 1 - spec/diagnostics_v1/vectors/stop_ack.bin | Bin 46 -> 0 bytes .../vectors/stop_ack.expected.json | 1 - .../vectors/stream_evidence.bin | Bin 231 -> 0 bytes .../vectors/stream_evidence.expected.json | 1 - .../diagnostics_v1/vectors/stream_failure.bin | Bin 68 -> 0 bytes .../vectors/stream_failure.expected.json | 1 - spec/diagnostics_v1/vectors/tactile.bin | Bin 149 -> 0 bytes .../vectors/tactile.expected.json | 1 - tests/test_diagnostics_contract.py | 261 --------- tools/generate_diagnostics_contract.py | 522 ------------------ 24 files changed, 820 deletions(-) delete mode 100644 spec/diagnostics_v1/README.md delete mode 100644 spec/diagnostics_v1/cases.json delete mode 100644 spec/diagnostics_v1/contract.json delete mode 100644 spec/diagnostics_v1/manifest.json delete mode 100644 spec/diagnostics_v1/vectors/boot_summary.bin delete mode 100644 spec/diagnostics_v1/vectors/boot_summary.expected.json delete mode 100644 spec/diagnostics_v1/vectors/device_context.bin delete mode 100644 spec/diagnostics_v1/vectors/device_context.expected.json delete mode 100644 spec/diagnostics_v1/vectors/imu.bin delete mode 100644 spec/diagnostics_v1/vectors/imu.expected.json delete mode 100644 spec/diagnostics_v1/vectors/mag.bin delete mode 100644 spec/diagnostics_v1/vectors/mag.expected.json delete mode 100644 spec/diagnostics_v1/vectors/start_ack.bin delete mode 100644 spec/diagnostics_v1/vectors/start_ack.expected.json delete mode 100644 spec/diagnostics_v1/vectors/stop_ack.bin delete mode 100644 spec/diagnostics_v1/vectors/stop_ack.expected.json delete mode 100644 spec/diagnostics_v1/vectors/stream_evidence.bin delete mode 100644 spec/diagnostics_v1/vectors/stream_evidence.expected.json delete mode 100644 spec/diagnostics_v1/vectors/stream_failure.bin delete mode 100644 spec/diagnostics_v1/vectors/stream_failure.expected.json delete mode 100644 spec/diagnostics_v1/vectors/tactile.bin delete mode 100644 spec/diagnostics_v1/vectors/tactile.expected.json delete mode 100644 tests/test_diagnostics_contract.py delete mode 100644 tools/generate_diagnostics_contract.py diff --git a/spec/diagnostics_v1/README.md b/spec/diagnostics_v1/README.md deleted file mode 100644 index 7e6d5a9..0000000 --- a/spec/diagnostics_v1/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# OGLO diagnostics v1 contract - -Status: **implementation-contract-not-hardware-captured**. - -This is the Phase 0 schema/vector freeze only. It makes no runtime parser, USB, -firmware, or SDK behavior change. `contract.json` freezes TAG1, TAG2, typed control -frames, admission/state rules, CONTEXT canonicalization, and the canonical manifest -preimage. `manifest.json` has an explicit `contract_inputs` hash view and a separate -complete generated-vector digest inventory, avoiding START_ACK hash self-reference. - -Existing `spec/vectors/tag_*.bin` files have `physical_capture` provenance. All -files in `vectors/` are deterministic synthetic conformance evidence, not board -captures. At default rates, TAG2 estimates 49,937.5 B/s (about 49,938): tactile -250 Hz plus IMU 500 Hz batched by 4 plus MAG 125 Hz batched by 4, excluding boundary -flush partial batches/control frames. This is an unqualified estimate, not a -throughput PASS: real hardware jitter and throughput capture remain required. - -Regenerate with `python tools/generate_diagnostics_contract.py`; verify without -writing with `python tools/generate_diagnostics_contract.py --check`. - -This directory is the only TAG2 protocol source of truth. Any SDK-facing -projection such as `spec/TAG_V2.json` must pin this manifest's -`contract_sha256` and `vector_set_sha256` and reproduce its header, sequence, -and typed-binary negotiation fields. An independent or ASCII-ACK TAG2 contract -is a conformance failure. diff --git a/spec/diagnostics_v1/cases.json b/spec/diagnostics_v1/cases.json deleted file mode 100644 index 9f20f98..0000000 --- a/spec/diagnostics_v1/cases.json +++ /dev/null @@ -1 +0,0 @@ -{"commands":{"fatal_order":["producer_boundary","drain_accepted_frames","STREAM_FAILURE","STREAM_EVIDENCE"],"fatal_stop_ack":"forbidden","invalid_hex":["53545245414d2054414732204f4e206e6f6e63653d3030313132323333343435353636373738383939616162626363646465656666","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660d0a","73747265616d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d205441473220204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839394141424243434444454546460a","53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566662065787472610a"],"transitions":[{"active_nonce":"","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"STARTING","reopen":false,"response":"new_start"},"lifecycle":"IDLE","name":"new_start","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"cached_start_ack"},"lifecycle":"ACTIVE","name":"same_start_active","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4e206e6f6e63653d66666565646463636262616139393838373736363535343433333232313130300a","expected":{"accepted":false,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"nonce_conflict"},"lifecycle":"ACTIVE","name":"different_start_active","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"STOPPING","reopen":false,"response":"stop_or_cached_stop_ack"},"lifecycle":"ACTIVE","name":"matching_stop","retired_nonces":[]},{"active_nonce":"","command_hex":"53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":false,"lifecycle":"IDLE","reopen":false,"response":"nonce_retired"},"lifecycle":"IDLE","name":"retired_start","retired_nonces":["00112233445566778899aabbccddeeff"]},{"active_nonce":"","command_hex":"53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","expected":{"accepted":true,"lifecycle":"IDLE","reopen":false,"response":"cached_stop_ack"},"lifecycle":"IDLE","name":"cached_stop","retired_nonces":["00112233445566778899aabbccddeeff"]},{"active_nonce":"","command_hex":"53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a","expected":{"accepted":true,"lifecycle":"IDLE","reopen":false,"response":"context_ack"},"lifecycle":"IDLE","name":"context_idle","retired_nonces":[]},{"active_nonce":"00112233445566778899aabbccddeeff","command_hex":"53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a","expected":{"accepted":false,"active_nonce":"00112233445566778899aabbccddeeff","lifecycle":"ACTIVE","reopen":false,"response":"command_rejected"},"lifecycle":"ACTIVE","name":"context_active","retired_nonces":[]}],"valid_hex":["53545245414d2054414732204f4e206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53545245414d2054414732204f4646206e6f6e63653d30303131323233333434353536363737383839396161626263636464656566660a","53455420434f4e544558542065794a6a59577870596e4a6864476c76626c397a614745794e5459694f69497a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a4d7a49697769593239755a6d6c6e58334e6f595449314e694936496a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49794d6a49694c434a6a623235305a5868305832567762324e6f496a6f334c434a7a5932686c625745694f694a765a3278764c6d526c646d6c6a5a56396a623235305a5868304c6e5978496e300a"]},"contexts":{"calibration_hash_golden":{"canonical_view_utf8":"{\"imu_bias_i16\":[0,0,0,0,0,0],\"mag_bias_i16\":[0,0,0],\"schema\":\"oglo.calibration_view.v1\",\"tactile_baseline_u16\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],\"tactile_threshold_u16\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}","domain_separator_hex":"4f474c4f2d43414c4942524154494f4e2d563100","preimage_hex":"4f474c4f2d43414c4942524154494f4e2d5631007b22696d755f626961735f693136223a5b302c302c302c302c302c305d2c226d61675f626961735f693136223a5b302c302c305d2c22736368656d61223a226f676c6f2e63616c6962726174696f6e5f766965772e7631222c2274616374696c655f626173656c696e655f753136223a5b302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c305d2c2274616374696c655f7468726573686f6c645f753136223a5b302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c302c305d7d","sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6"},"config_hash_golden":{"canonical_view_utf8":"{\"firmware_schema\":6,\"firmware_semver\":{\"major\":0,\"minor\":9,\"patch\":12},\"hardware_model_id\":1330072655,\"imu\":{\"axes\":[\"ax\",\"ay\",\"az\",\"gx\",\"gy\",\"gz\"],\"rate_hz\":500,\"raw\":\"i16le\"},\"mag\":{\"axes\":[\"mx\",\"my\",\"mz\"],\"rate_hz\":125,\"raw\":\"i16le\"},\"schema\":\"oglo.config_view.v1\",\"tactile\":{\"order\":\"finger,row,col\",\"packing\":\"packed12\",\"rate_hz\":250,\"taxel_count\":80},\"tag_ver_max\":2}","domain_separator_hex":"4f474c4f2d434f4e4649472d563100","preimage_hex":"4f474c4f2d434f4e4649472d5631007b226669726d776172655f736368656d61223a362c226669726d776172655f73656d766572223a7b226d616a6f72223a302c226d696e6f72223a392c227061746368223a31327d2c2268617264776172655f6d6f64656c5f6964223a313333303037323635352c22696d75223a7b2261786573223a5b226178222c226179222c22617a222c226778222c226779222c22677a225d2c22726174655f687a223a3530302c22726177223a226931366c65227d2c226d6167223a7b2261786573223a5b226d78222c226d79222c226d7a225d2c22726174655f687a223a3132352c22726177223a226931366c65227d2c22736368656d61223a226f676c6f2e636f6e6669675f766965772e7631222c2274616374696c65223a7b226f72646572223a2266696e6765722c726f772c636f6c222c227061636b696e67223a227061636b65643132222c22726174655f687a223a3235302c22746178656c5f636f756e74223a38307d2c227461675f7665725f6d6178223a327d","sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2"},"context_sha256":"8f408c6edcb0453f97497c1b0785d036883d1f3b9200c0e4ce862639cde5a46e","hash_admission":{"matching":"accept","mismatch":"fatal_stream_failure_no_tag1_fallback"},"invalid":{"bad_epoch":"{\"context_epoch\":-1}","bad_hash":"{\"calibration_sha256\":\"xyz\"}","bad_utf8":"_w","non_nfc":"{\"schema\":\"oglo.device_context.v1\",\"recording_id\":\"e\\u0301\"}","non_url_safe":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn+","null":"{\"calibration_sha256\":null}","padded_base64url":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn0=","uppercase_hash":"{\"calibration_sha256\":\"A\"}"},"max_valid_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"recording_id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"schema\":\"oglo.device_context.v1\"}","max_valid_command_bytes":1379,"max_valid_token_bytes":1366,"max_valid_utf8_bytes":1024,"oversize_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"recording_id\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\"schema\":\"oglo.device_context.v1\"}","valid_canonical_json":"{\"calibration_sha256\":\"3333333333333333333333333333333333333333333333333333333333333333\",\"config_sha256\":\"2222222222222222222222222222222222222222222222222222222222222222\",\"context_epoch\":7,\"schema\":\"oglo.device_context.v1\"}","valid_set_context_base64url":"eyJjYWxpYnJhdGlvbl9zaGEyNTYiOiIzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzIiwiY29uZmlnX3NoYTI1NiI6IjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJjb250ZXh0X2Vwb2NoIjo3LCJzY2hlbWEiOiJvZ2xvLmRldmljZV9jb250ZXh0LnYxIn0"},"negotiation_cases":[{"derived":{"accepted":true,"fail_closed":false,"no_tag1_fallback":true,"reason":"accepted","stream_id":72623859790382856,"tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","lifecycle":"STARTING","malformed":0,"name":"start_positive","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_nonce","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010299999999999999999999999999999999102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555552a25e8e8","lifecycle":"STARTING","malformed":0,"name":"start_wrong_nonce","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_protocol_version","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010100112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555545e652e6","lifecycle":"STARTING","malformed":0,"name":"start_wrong_version","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_contract_sha256","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444447777777777777777777777777777777777777777777777777777777777777777cc95fb51","lifecycle":"STARTING","malformed":0,"name":"start_wrong_contract","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_context_sha256","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000006666666666666666666666666666666666666666666666666666666666666666555555555555555555555555555555555555555555555555555555555555555597818365","lifecycle":"STARTING","malformed":0,"name":"start_wrong_context","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"zero_boot_id","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff0000000000000000000000000000000011111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555660e6b82","lifecycle":"STARTING","malformed":0,"name":"start_zero_boot","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"zero_stream_id","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000000000000000000000200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555d4fe2661","lifecycle":"STARTING","malformed":0,"name":"start_zero_stream","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"no_valid_start_ack","stream_id":"","tail_packets":[]},"input_hex":"a55b83850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f11111111111111111111111111111111111111111111111111111111111111110700000044444444444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555ab2beae7","lifecycle":"STARTING","malformed":1,"name":"start_wrong_first_type","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"wrong_lifecycle","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","lifecycle":"ACTIVE","malformed":0,"name":"start_wrong_lifecycle","remainder_hex":""},{"derived":{"accepted":false,"fail_closed":true,"no_tag1_fallback":true,"reason":"no_valid_start_ack","stream_id":"","tail_packets":[]},"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc1","lifecycle":"STARTING","malformed":1,"name":"start_crc","remainder_hex":""}],"parser_cases":[{"expected_admission":{"accepted":[2],"control_replays":0,"duplicates":0,"expected_seq":3,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":2,"input_hex":"a55b01780002000000030000000200000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422438acee419","name":"valid_data"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":2,"input_hex":"a55b01780002000000030000000200000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422438acee418","name":"crc_data_reject_continue"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc1","name":"crc_start_ack_malformed"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":1},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"1230066625199609624","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b01780000000000040000000200000018171615141312111f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f24024124224390104333","name":"wrong_stream_stale"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b63000000000000050000000200000008070605040302015be61f5e","name":"unknown_type"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":1,"expected_packets":[],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b01010000000000060000000200000008070605040302010087013de7","name":"bad_type_length"},{"expected_admission":{"accepted":[0,1],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128},{"seq":1,"stream_id":"72623859790382856","type":131}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe0f0300030000004f4c474f020600090c11111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333333333333307000000629e3116","name":"coalesced_start_boot"},{"expected_admission":{"accepted":[0],"control_replays":0,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128}],"expected_remainder_hex":"a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0a55b8383000100000002000000020000000807060504030201102132435465768798a9bacbdcedfe","name":"coalesced_start_partial_boot"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a5","initial_expected_seq":0,"input_hex":"a5","name":"split_start_1"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b80850000000000010000000200000008070605040302","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302","name":"split_start_24"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b8085000000000001000000020000000807060504030201","initial_expected_seq":0,"input_hex":"a55b8085000000000001000000020000000807060504030201","name":"split_start_25"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":0,"expected_seq":0,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[],"expected_remainder_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444","initial_expected_seq":0,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f111111111111111111111111111111111111111111111111111111111111111107000000444444444444","name":"split_start_100"},{"expected_admission":{"accepted":[0,1],"control_replays":0,"duplicates":0,"expected_seq":2,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":1,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800010000000b0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f24024124224350723e74","name":"normal"},{"expected_admission":{"accepted":[0,2],"control_replays":0,"duplicates":0,"expected_seq":3,"fail_closed":false,"gaps":1,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800020000000c0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422435da6917e","name":"forward_gap"},{"expected_admission":{"accepted":[0],"control_replays":0,"duplicates":1,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":1},{"seq":0,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":0,"input_hex":"a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726a55b017800000000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243f35ea726","name":"duplicate"},{"expected_admission":{"accepted":[],"control_replays":0,"duplicates":1,"expected_seq":3,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":2,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"","initial_expected_seq":3,"input_hex":"a55b017800020000000a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243415717fd","name":"out_of_order"},{"expected_admission":{"accepted":[4294967294,4294967295,0],"control_replays":0,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":4294967294,"stream_id":"72623859790382856","type":1},{"seq":4294967295,"stream_id":"72623859790382856","type":1},{"seq":0,"stream_id":"72623859790382856","type":1}],"expected_remainder_hex":"a5","initial_expected_seq":4294967294,"input_hex":"a55b017800feffffff0a0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f2402412422434bbef77aa55b017800ffffffff0b0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243e8926e28a55b017800000000000c0000000000000008070605040302011f41f51f61f71f81f91fa1fb1fc1fd1fe1ff20020120220320420520620720820920a20b20c20d20e20f21021121221321421521621721821921a21b21c21d21e21f22022122222322422522622722822922a22b22c22d22e22f23023123223323423523623723823923a23b23c23d23e23f240241242243efaf21a5","name":"wrap"},{"expected_admission":{"accepted":[],"control_replays":1,"duplicates":0,"expected_seq":1,"fail_closed":false,"gaps":0,"no_tag1_fallback":true,"stale":0},"expected_malformed":0,"expected_packets":[{"seq":0,"stream_id":"72623859790382856","type":128}],"expected_remainder_hex":"","initial_expected_seq":1,"input_hex":"a55b80850000000000010000000200000008070605040302010200112233445566778899aabbccddeeff102132435465768798a9bacbdcedfe0f1111111111111111111111111111111111111111111111111111111111111111070000004444444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555567790cc0","name":"cached_start_ack"}],"status":"synthetic_conformance_cases_not_hardware_captures","timing":{"global_wire_header_monotonic_required":false,"imu_default_offsets_us":[0,2000,4000,6000],"imu_flush_deadline_ms":8,"mag_default_offsets_us":[0,8000,16000,24000],"mag_flush_deadline_ms":32,"per_modality_reconstructed_sample_monotonic_required":true}} diff --git a/spec/diagnostics_v1/contract.json b/spec/diagnostics_v1/contract.json deleted file mode 100644 index ac69b1f..0000000 --- a/spec/diagnostics_v1/contract.json +++ /dev/null @@ -1 +0,0 @@ -{"admission":{"active":"same START nonce retry returns byte-identical cached ACK/current stream; different nonce gives nonfatal nonce_conflict/no transition; only matching STOP accepted; other commands/context mutation nonfatal command_rejected; context/topology mutation uses fatal terminal order","capability":"GET CONFIG numeric tag_ver_max; missing is TAG1-only proof only for validated legacy firmware <=0.9.12/schema6; 1 selects TAG1; >=2 selects min(host_max,device_max); malformed_or_contradictory_fails_closed","command_encoding":"strict ASCII, exact uppercase and single spaces, one LF; CRLF/leading/trailing/extra tokens rejected","commands":{"context":"SET CONTEXT \n","start":"STREAM TAG2 ON nonce=<32 lowercase hex>\n","stop":"STREAM TAG2 OFF nonce=\n"},"host":"CRC-valid wrong-stream frame is stale evidence: discard and continue without changing current expected seq/time/counters; never TAG1 fallback","parser_resync":"scan negotiated magic only; retain incomplete frame exactly and one possible magic-prefix byte when no complete magic; invalid type/length/CRC advances scan by one byte after the first magic byte; never reinterpret TAG2 as TAG1","receiver_sequence":"expected seq modulo 2^32; exact accepts; forward distance <2^31 records gap then accepts; backward distance is duplicate/out-of-order; cached control replay neither advances nor gaps","start":"first valid frame after ON is START_ACK matching nonce, stream_id, contract_sha256; preserve fragment/coalesced tail; invalid ACK/CRC/wrong stream never downgrades","stop":"same STOP retry returns cached STOP_ACK within boot; retired START nonce cannot reopen; new nonce after terminal gets new stream_id"},"canonicalization":{"generated_vector_hashes":"inventory only; excluded from contract_sha256 preimage to avoid START_ACK self-reference","hash_preimage":"SHA256(b'OGLO-DIAGNOSTICS-CONTRACT-V1\\0' + canonical_json(manifest.contract_inputs))","json":"UTF-8,NFC,LF,object_keys_sorted,compact_separators,no_duplicate_keys,no_null,no_NaN,no_float,unknown_optional_omitted; runtime_u64_and_ns_use_decimal_strings_when_JSON"},"context":{"ack":"#CONTEXT ok sha256=<64 lowercase hex>\\n","active":"ACTIVE rejects mutation through typed STREAM_FAILURE command_rejected","calibration_sha256":{"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":"4f474c4f2d43414c4942524154494f4e2d563100","golden_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","view":{"additional_fields":false,"fields":{"imu_bias_i16":"array[6]","mag_bias_i16":"array[3]","tactile_baseline_u16":"array[80]","tactile_threshold_u16":"array[80]"},"schema":"oglo.calibration_view.v1"}},"config_sha256":{"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":"4f474c4f2d434f4e4649472d563100","golden_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","view":{"additional_fields":false,"fields":{"firmware_schema":"u8","firmware_semver":"{major:u8,minor:u8,patch:u8}","hardware_model_id":"u32","imu":{"axes":"array[ax,ay,az,gx,gy,gz]","rate_hz":"u16","raw":"i16le"},"mag":{"axes":"array[mx,my,mz]","rate_hz":"u16","raw":"i16le"},"tactile":{"order":"finger,row,col","packing":"packed12","rate_hz":"u16","taxel_count":"80"},"tag_ver_max":"u8"},"schema":"oglo.config_view.v1"}},"context_sha256":{"algorithm":"SHA256","preimage":"canonical UTF-8 bytes without LF"},"epoch_ownership":{"config_epoch":"firmware: starts 1 after boot NVS load; increments once per successful config or calibration mutation outside ACTIVE","context_epoch":"host: supplied only by SET CONTEXT and independent of config_epoch"},"evidence":"ACK hash must equal START_ACK and DEVICE_CONTEXT context_sha256; mismatch is typed fatal termination/no TAG1 fallback","max_base64url_bytes":1366,"max_set_context_command_bytes":1379,"max_utf8_bytes":1024,"optional_omit_when_unknown":["capture_session_id","recording_id","fault_injection_id"],"rejection":"#CONTEXT error code=command_rejected\\n","required":["schema","context_epoch","config_sha256","calibration_sha256"],"rules":"no_nulls; strings_NFC; hashes_lowercase_64_hex; u32_context_epoch_host_published_only; UTF-8_sorted_keys_compact_separators","schema":"oglo.device_context.v1","set_command":"INACTIVE-only exact ASCII SET CONTEXT \\n","writer":"single_TX_writer_when_implemented"},"schema":"oglo.diagnostics_contract.v1","scope":"phase_0_schema_and_vectors_only_no_runtime_behavior_changes","status":"implementation-contract-not-hardware-captured","tag1":{"crc":"none","header_fields":[{"constant":"a55a","endianness":"n/a","length":2,"name":"magic","offset":0,"semantic":"TAG1 magic A5 5A","wire_type":"bytes"},{"endianness":"little","length":1,"name":"type","offset":2,"semantic":"frame type","valid":"1 tactile|2 imu|3 mag","wire_type":"u8"},{"endianness":"little","length":2,"name":"payload_len","offset":3,"semantic":"payload bytes","valid":"120|12|6 by type","wire_type":"u16"},{"endianness":"little","length":4,"name":"seq","offset":5,"semantic":"per modality sequence","wire_type":"u32"},{"endianness":"little","length":4,"name":"timestamp_us","offset":9,"semantic":"per modality timestamp","wire_type":"u32"}],"header_format":"<2sBHII","header_len":13,"magic_hex":"a55a","physical_vectors":["spec/vectors/tag_tactile_133b.bin","spec/vectors/tag_imu_25b.bin","spec/vectors/tag_mag_19b.bin"],"sequence_scope":"per_frame_type","types":{"1":{"fields":[{"endianness":"little","formula":"a=b0<<4|b1>>4; b=(b1&0x0f)<<8|b2","length":120,"name":"taxels","offset":0,"semantic":"80 finger,row,col counts","wire_type":"packed_u12_be_pairs"}],"name":"tactile","payload_len":120},"2":{"fields":[{"endianness":"little","length":2,"name":"ax","offset":0,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"ay","offset":2,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"az","offset":4,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gx","offset":6,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gy","offset":8,"semantic":"raw IMU axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"gz","offset":10,"semantic":"raw IMU axis","wire_type":"i16"}],"name":"imu","payload_len":12},"3":{"fields":[{"endianness":"little","length":2,"name":"mx","offset":0,"semantic":"raw magnetometer axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"my","offset":2,"semantic":"raw magnetometer axis","wire_type":"i16"},{"endianness":"little","length":2,"name":"mz","offset":4,"semantic":"raw magnetometer axis","wire_type":"i16"}],"name":"mag","payload_len":6}}},"tag2":{"batch_flush_deadlines_ms":{"imu":8,"mag":32},"crc32":{"algorithm":"CRC-32/ISO-HDLC","check_hex":"cbf43926","coverage":"exact_header_plus_payload","init_hex":"ffffffff","reference":"zlib.crc32","reflected_polynomial_hex":"edb88320","xorout_hex":"ffffffff"},"device_time_us":"actual monotonic u64; data header is first sample time and sample=header+dt; per-modality reconstructed sample times strictly increase; global wire header times may move backward and are not causal order","header_fields":[{"constant":"a55b","endianness":"n/a","length":2,"name":"magic","offset":0,"semantic":"TAG2 magic A5 5B","wire_type":"bytes"},{"endianness":"little","invalid":"unknown type rejected","length":1,"name":"type","offset":2,"semantic":"frame type","wire_type":"u8"},{"endianness":"little","invalid":"0..512 and exact type layout","length":2,"name":"payload_len","offset":3,"semantic":"payload bytes only","wire_type":"u16"},{"endianness":"little","length":4,"name":"seq","offset":5,"rule":"one space per stream_id","semantic":"global admitted TX sequence","wire_type":"u32"},{"endianness":"little","length":8,"name":"device_time_us","offset":9,"semantic":"data first sample/control event time","wire_type":"u64"},{"endianness":"little","invalid":"zero rejected","length":8,"name":"stream_id","offset":17,"semantic":"nonzero random boot-local stream","wire_type":"u64"}],"header_format":"<2sBHIQQ","header_len":25,"idempotent_control_duplicates":"same-nonce START replay only in STARTING/ACTIVE; matching STOP retry replays cached STOP_ACK within the same boot; retired nonce never reopens","lifecycle_order":"START_ACK first; BOOT_SUMMARY then DEVICE_CONTEXT before data; requested STOP: producer boundary blocks new producer admission, drain accepted FIFO, STREAM_EVIDENCE, STOP_ACK last; fatal context/topology: producer boundary blocks new admission, drain accepted FIFO, STREAM_FAILURE, STREAM_EVIDENCE, no successful STOP_ACK; failure/evidence seq allocated after drained FIFO","magic_hex":"a55b","max_payload_len":512,"physical_rollout_gates":{"estimated_steady_wire_bps_max":55000,"normal_run_required_zero":["queue_drops","malformed_frames","crc_failures","wrong_stream_frames"],"queue_ram_cap":"must_freeze_before_phase5_approval","throughput_jitter_status":"unqualified_until_real_hardware"},"sequence_scope":"one_u32_space_per_stream_id_in_TX_queue_wire_admission_order; START_ACK_seq_0; modulo_2^32; atomic_sequence_allocation_and_queue_admission; failed_queue_admission_does_not_consume_seq","stream_id":"nonzero_random_u64_not_reused_within_one_mcu_boot","trailer_format":">4; b=(b1&0x0f)<<8|b2; 0..4095","length":120,"name":"taxels","offset":0,"semantic":"80 counts in finger,row,col order","wire_type":"packed_u12_be_pairs"}],"name":"tactile","payload_format":"packed12_80_taxels","payload_len":120},"128":{"fields":[{"constant":2,"endianness":"little","length":1,"name":"protocol_version","offset":0,"semantic":"must be 2","wire_type":"u8"},{"endianness":"n/a","length":16,"name":"nonce","offset":1,"semantic":"command nonce","wire_type":"bytes"},{"endianness":"n/a","invalid":"zero","length":16,"name":"mcu_boot_id","offset":17,"semantic":"random boot ID","wire_type":"bytes"},{"endianness":"n/a","length":32,"name":"application_sha256","offset":33,"semantic":"firmware image digest","wire_type":"bytes"},{"endianness":"little","length":4,"name":"config_epoch","offset":65,"semantic":"firmware config epoch","wire_type":"u32"},{"endianness":"n/a","length":32,"name":"context_sha256","offset":69,"semantic":"host canonical context digest","wire_type":"bytes"},{"endianness":"n/a","length":32,"name":"contract_sha256","offset":101,"semantic":"contract input hash","wire_type":"bytes"}],"name":"START_ACK","payload_format":"?x`Nj@(%KvL`>j6SCH-G>D diff --git a/spec/diagnostics_v1/vectors/boot_summary.expected.json b/spec/diagnostics_v1/vectors/boot_summary.expected.json deleted file mode 100644 index 3f6c96d..0000000 --- a/spec/diagnostics_v1/vectors/boot_summary.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590044592","expected":{"header":{"device_time_us":"8590044592","magic_hex":"a55b","payload_len":131,"seq":1,"stream_id":"72623859790382856","type":131},"payload":{"application_sha256":"1111111111111111111111111111111111111111111111111111111111111111","calibration_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","config_epoch":7,"config_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","firmware_schema":6,"firmware_semver_major":0,"firmware_semver_minor":9,"firmware_semver_patch":12,"hardware_model_id":1330072655,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","reset_reason":2,"reset_reason_raw":2864434397,"tag_ver_max":2}},"name":"boot_summary","payload_len":131,"seq":1,"stream_id":"72623859790382856","type":131} diff --git a/spec/diagnostics_v1/vectors/device_context.bin b/spec/diagnostics_v1/vectors/device_context.bin deleted file mode 100644 index c37c337843f6a347291bbb620b69360b9cd0d580..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 160 zcmZ1)-O|j!#K6FC;0hy{=3r-IWnpGw6i_sB4oNL*pRsb+={s-#@w*TJEF#r+F3GL= zyWlX-v${Vg3cek(*=zoHSF?eO!@cR#WIpZeXbR9UIJZBl`16zrMV00PHhV7yZGPb$ gD!F^x3j3UI>_9hw@GpihjI|5_42+D7>fxI;07PRtnE(I) diff --git a/spec/diagnostics_v1/vectors/device_context.expected.json b/spec/diagnostics_v1/vectors/device_context.expected.json deleted file mode 100644 index 61bead9..0000000 --- a/spec/diagnostics_v1/vectors/device_context.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590054592","expected":{"header":{"device_time_us":"8590054592","magic_hex":"a55b","payload_len":131,"seq":2,"stream_id":"72623859790382856","type":132},"payload":{"calibration_sha256":"b98882502830cebf5a73f39490727937103cbdd152b3e84b5519bbb6a83f6cf6","config_epoch":7,"config_sha256":"04147bdcd21eacfda0c30ce67efcc870f6c23cbd37fdba83304440de97961cf2","context_epoch":7,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","imu_axes_id":1,"imu_hz":500,"mag_axes_id":1,"mag_hz":125,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","tactile_hz":250,"tactile_packing_id":1,"taxel_count":80}},"name":"device_context","payload_len":131,"seq":2,"stream_id":"72623859790382856","type":132} diff --git a/spec/diagnostics_v1/vectors/imu.bin b/spec/diagnostics_v1/vectors/imu.bin deleted file mode 100644 index 797928ec2c2fb8b42d63e0bea05e2b62935078b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86 zcmZ1)&1A{I!oa}rK#7Tg2}pCWv$3);Gckh11c3O%5+D)t{{lOs03)2YfS*YKs2D0& PAkHkn4Cfttot_T>a9I-y diff --git a/spec/diagnostics_v1/vectors/imu.expected.json b/spec/diagnostics_v1/vectors/imu.expected.json deleted file mode 100644 index 9955a7f..0000000 --- a/spec/diagnostics_v1/vectors/imu.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590074592","expected":{"header":{"device_time_us":"8590074592","magic_hex":"a55b","payload_len":57,"seq":4,"stream_id":"72623859790382856","type":2},"payload":{"count":4,"records":[{"ax":4096,"ay":0,"az":-4096,"dt_us":0,"gx":164,"gy":0,"gz":-164},{"ax":4097,"ay":1,"az":-4096,"dt_us":2000,"gx":164,"gy":0,"gz":-164},{"ax":4098,"ay":2,"az":-4096,"dt_us":4000,"gx":164,"gy":0,"gz":-164},{"ax":4099,"ay":3,"az":-4096,"dt_us":6000,"gx":164,"gy":0,"gz":-164}]}},"name":"imu","payload_len":57,"seq":4,"stream_id":"72623859790382856","type":2} diff --git a/spec/diagnostics_v1/vectors/mag.bin b/spec/diagnostics_v1/vectors/mag.bin deleted file mode 100644 index 6614e237ef6fe8e52542b6d1b185b18e44d1cc84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62 zcmZ1)&8*14%D}+z!IO!B2}pCWv$3);GcmF-Fzk|IU|9UgL4LOsBam*e+atvUqz}aI Nm0||cjK>Zq0|0QJ5M}@X diff --git a/spec/diagnostics_v1/vectors/mag.expected.json b/spec/diagnostics_v1/vectors/mag.expected.json deleted file mode 100644 index 3641863..0000000 --- a/spec/diagnostics_v1/vectors/mag.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590084592","expected":{"header":{"device_time_us":"8590084592","magic_hex":"a55b","payload_len":33,"seq":5,"stream_id":"72623859790382856","type":3},"payload":{"count":4,"records":[{"dt_us":0,"mx":6842,"my":0,"mz":-3421},{"dt_us":8000,"mx":6843,"my":1,"mz":-3421},{"dt_us":16000,"mx":6844,"my":2,"mz":-3421},{"dt_us":24000,"mx":6845,"my":3,"mz":-3421}]}},"name":"mag","payload_len":33,"seq":5,"stream_id":"72623859790382856","type":3} diff --git a/spec/diagnostics_v1/vectors/start_ack.bin b/spec/diagnostics_v1/vectors/start_ack.bin deleted file mode 100644 index 1802a162e7a477f6aae51c4cbbc69a8bcfebe672..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmZ1)-O$Pa0t?z08JK`H2Rj=p3o{cV6N8|Vu}f%LdB@CEyU*Nx_g_HK$T=jntbNAH zU8nE7{l_mz0I&lMb0GlyZNIcm<+HecG|KH>#-C^RT3KIh`4{dZ+WOvwaraARx$fwk FTL43xFqQxS diff --git a/spec/diagnostics_v1/vectors/start_ack.expected.json b/spec/diagnostics_v1/vectors/start_ack.expected.json deleted file mode 100644 index 55dbc8a..0000000 --- a/spec/diagnostics_v1/vectors/start_ack.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590034592","expected":{"header":{"device_time_us":"8590034592","magic_hex":"a55b","payload_len":133,"seq":0,"stream_id":"72623859790382856","type":128},"payload":{"application_sha256":"1111111111111111111111111111111111111111111111111111111111111111","config_epoch":7,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","contract_sha256":"4f3df485950e38d7c55a46de68fce6de8505eab4fe574c1585ef4401bbe9031e","mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","nonce_hex":"00112233445566778899aabbccddeeff","protocol_version":2}},"name":"start_ack","payload_len":133,"seq":0,"stream_id":"72623859790382856","type":128} diff --git a/spec/diagnostics_v1/vectors/stop_ack.bin b/spec/diagnostics_v1/vectors/stop_ack.bin deleted file mode 100644 index 7200c56fcb6c143eb3371be87f3e487dd1187c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46 zcmZ1)-6+Vw!N9WhT($em-FN>PTtUByYK!BC>lA3q?WbM zSh?%;owxt^7ql^g)iV@A^nuJk#x4{Bpm7XrK+K4QL1HXO7#klX1_L0y3=GRTUK#@c D39u)= diff --git a/spec/diagnostics_v1/vectors/stream_evidence.expected.json b/spec/diagnostics_v1/vectors/stream_evidence.expected.json deleted file mode 100644 index 329175c..0000000 --- a/spec/diagnostics_v1/vectors/stream_evidence.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590104592","expected":{"header":{"device_time_us":"8590104592","magic_hex":"a55b","payload_len":202,"seq":7,"stream_id":"72623859790382856","type":133},"payload":{"counter_saturated":0,"deadline_misses":0,"end_config_epoch":7,"end_context_epoch":7,"end_context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","end_time_us":"8590094592","evidence_counts_as_transmitted":0,"first_admitted_seq":0,"imu_dropped_samples":0,"imu_enqueued_samples":4,"imu_produced_samples":4,"imu_transmitted_samples":4,"last_admitted_seq":6,"mag_dropped_samples":0,"mag_enqueued_samples":4,"mag_produced_samples":4,"mag_transmitted_samples":4,"mcu_boot_id_hex":"102132435465768798a9bacbdcedfe0f","nonce_hex":"00112233445566778899aabbccddeeff","queue_drops":0,"reserved":0,"short_writes":0,"start_config_epoch":7,"start_context_epoch":7,"start_context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","start_time_us":"8590034592","stop_ack_counts_as_transmitted":0,"tactile_dropped_samples":0,"tactile_enqueued_samples":1,"tactile_produced_samples":1,"tactile_transmitted_samples":1,"terminal_reason":1}},"name":"stream_evidence","payload_len":202,"seq":7,"stream_id":"72623859790382856","type":133} diff --git a/spec/diagnostics_v1/vectors/stream_failure.bin b/spec/diagnostics_v1/vectors/stream_failure.bin deleted file mode 100644 index 232160b67e55c3eccdc42673bd3ef9bd0962d33e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68 qcmZ1)-K5UI#sCC`Obkpwii4evm4%s!k&%Ic6UcQT0P_3J+yDSa*bQ_5 diff --git a/spec/diagnostics_v1/vectors/stream_failure.expected.json b/spec/diagnostics_v1/vectors/stream_failure.expected.json deleted file mode 100644 index 55c9012..0000000 --- a/spec/diagnostics_v1/vectors/stream_failure.expected.json +++ /dev/null @@ -1 +0,0 @@ -{"device_time_us":"8590094592","expected":{"header":{"device_time_us":"8590094592","magic_hex":"a55b","payload_len":39,"seq":6,"stream_id":"72623859790382856","type":130},"payload":{"code":1,"context_sha256":"4444444444444444444444444444444444444444444444444444444444444444","detail":9,"fatal":0}},"name":"stream_failure","payload_len":39,"seq":6,"stream_id":"72623859790382856","type":130} diff --git a/spec/diagnostics_v1/vectors/tactile.bin b/spec/diagnostics_v1/vectors/tactile.bin deleted file mode 100644 index dfc1cb6d7997eb310abba04a6b9c9c40c59336c8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149 zcmZ1)%~-*}%)r2K;Wr}#6OiU$XJchyW@40g{3@ULUB2 2**32 and stream != 0 - expected = json.loads((SPEC / "vectors" / f"{name}.expected.json").read_text()) - assert (kind, seq, str(clock), str(stream), plen) == (expected["type"], expected["seq"], expected["device_time_us"], expected["stream_id"], expected["payload_len"]) - seqs.append(seq) - if kind in (2, 3): - count = raw[25]; stride = 14 if kind == 2 else 8 - assert 1 <= count <= 8 and plen == 1 + stride * count - offsets = [struct.unpack_from("= {"header"} - assert expected["expected"].get("payload") or expected["expected"].get("payload_hex") - - -def test_canonical_context_profile_and_negative_examples(): - cases = json.loads((SPEC / "cases.json").read_text())["contexts"] - valid = cases["valid_canonical_json"] - assert gen.validate_context(valid)["schema"] == "oglo.device_context.v1" - token = cases["valid_set_context_base64url"] - assert "=" not in token and base64.urlsafe_b64decode(token + "==").decode() == valid - for name, raw in cases["invalid"].items(): - try: - if name in ("padded_base64url", "non_url_safe", "bad_utf8"): - gen.decode_context_token(raw) - else: - gen.validate_context(raw) - except ValueError: pass - else: raise AssertionError(raw) - for bad in ('{"x":null}', '{"x":1.0}', '{"e\\u0301":1}', '{"x":1,"x":2}'): - try: gen.canonical(gen.strict_json(bad)) - except ValueError: pass - else: raise AssertionError(bad) - - -def test_control_vectors_decode_every_named_field_and_epoch_owner(): - contract = json.loads((SPEC / "contract.json").read_text()) - expected = { - name: json.loads((SPEC / "vectors" / f"{name}.expected.json").read_text())["expected"]["payload"] - for name in ("start_ack", "stop_ack", "stream_failure", "boot_summary", "device_context", "stream_evidence") - } - boot = expected["boot_summary"] - assert (boot["firmware_semver_major"], boot["firmware_semver_minor"], boot["firmware_semver_patch"]) == (0, 9, 12) - assert boot["reset_reason"] == 2 and boot["config_epoch"] == 7 - assert len((SPEC / "vectors" / "boot_summary.bin").read_bytes()) == 25 + 131 + 4 - context = expected["device_context"] - assert (context["config_epoch"], context["context_epoch"]) == (7, 7) - assert (context["taxel_count"], context["tactile_packing_id"], context["imu_axes_id"], context["mag_axes_id"]) == (80, 1, 1, 1) - assert len((SPEC / "vectors" / "device_context.bin").read_bytes()) == 25 + 131 + 4 - evidence = expected["stream_evidence"] - for modality, total in (("tactile", 1), ("imu", 4), ("mag", 4)): - assert evidence[f"{modality}_produced_samples"] == total - assert evidence[f"{modality}_enqueued_samples"] == total - assert evidence[f"{modality}_transmitted_samples"] == total - assert evidence[f"{modality}_dropped_samples"] == 0 - assert evidence["queue_drops"] == evidence["short_writes"] == evidence["deadline_misses"] == 0 - assert evidence["evidence_counts_as_transmitted"] == evidence["stop_ack_counts_as_transmitted"] == 0 - assert evidence["reserved"] == 0 - assert contract["context"]["epoch_ownership"]["config_epoch"].startswith("firmware") - assert contract["context"]["epoch_ownership"]["context_epoch"].startswith("host") - for kind in (128, 129, 130, 131, 132, 133): - fields = contract["tag2"]["types"][str(kind)]["fields"] - assert all({"name", "offset", "wire_type", "length", "semantic"} <= set(field) for field in fields) - - -def test_executable_parser_admission_vectors_and_tail_preservation(): - cases = json.loads((SPEC / "cases.json").read_text()) - by_name = {case["name"]: case for case in cases["parser_cases"]} - for case in cases["parser_cases"]: - packets, remainder, malformed = gen.parse_tag2(bytes.fromhex(case["input_hex"])) - cached = () - if case["name"] == "cached_start_ack": - packet = packets[0] - cached = ((packet["type"], packet["seq"], packet["payload"]),) - state = gen.admit(packets, gen.STREAM, case["initial_expected_seq"], cached) - assert remainder.hex() == case["expected_remainder_hex"] - assert malformed == case["expected_malformed"] - assert state == case["expected_admission"] - assert by_name["crc_data_reject_continue"]["expected_malformed"] == 1 - assert by_name["crc_start_ack_malformed"]["expected_malformed"] == 1 - assert by_name["wrong_stream_stale"]["expected_admission"]["stale"] == 1 - assert by_name["wrong_stream_stale"]["expected_admission"]["expected_seq"] == 0 - assert by_name["forward_gap"]["expected_admission"]["gaps"] == 1 - assert by_name["duplicate"]["expected_admission"]["duplicates"] == 1 - assert by_name["wrap"]["expected_admission"]["expected_seq"] == 1 - assert by_name["cached_start_ack"]["expected_admission"]["control_replays"] == 1 - partial = by_name["coalesced_start_partial_boot"] - assert len(partial["expected_packets"]) == 1 and partial["expected_remainder_hex"] - assert all(case["expected_admission"]["no_tag1_fallback"] for case in cases["parser_cases"]) - - -def test_exact_command_grammar_nonce_lifecycle_and_fatal_order(): - cases = json.loads((SPEC / "cases.json").read_text())["commands"] - assert [gen.validate_command(bytes.fromhex(raw))[0] for raw in cases["valid_hex"]] == ["start", "stop", "context"] - for raw in cases["invalid_hex"]: - try: - gen.validate_command(bytes.fromhex(raw)) - except ValueError: - pass - else: - raise AssertionError(raw) - transitions = {item["name"]: item for item in cases["transitions"]} - for item in transitions.values(): - active = item["active_nonce"] or None - actual = gen.command_transition(item["lifecycle"], bytes.fromhex(item["command_hex"]), active_nonce=active, retired_nonces=tuple(item["retired_nonces"])) - assert actual == item["expected"] - assert transitions["same_start_active"]["expected"]["response"] == "cached_start_ack" - assert transitions["retired_start"]["expected"]["response"] == "nonce_retired" - assert transitions["retired_start"]["expected"]["reopen"] is False - assert transitions["different_start_active"]["expected"]["accepted"] is False - assert cases["fatal_order"] == ["producer_boundary", "drain_accepted_frames", "STREAM_FAILURE", "STREAM_EVIDENCE"] - assert cases["fatal_stop_ack"] == "forbidden" - - -def test_context_byte_limits_hash_preimages_and_physical_gates(): - cases = json.loads((SPEC / "cases.json").read_text())["contexts"] - contract = json.loads((SPEC / "contract.json").read_text()) - maximum = cases["max_valid_canonical_json"] - assert len(maximum.encode()) == gen.CONTEXT_MAX_UTF8 == cases["max_valid_utf8_bytes"] - assert cases["max_valid_token_bytes"] == 1366 - assert cases["max_valid_command_bytes"] == gen.CONTEXT_COMMAND_MAX == 1379 - gen.validate_context(maximum) - try: - gen.validate_context(cases["oversize_canonical_json"]) - except ValueError: - pass - else: - raise AssertionError("1025-byte context accepted") - assert hashlib.sha256(cases["valid_canonical_json"].encode()).hexdigest() == cases["context_sha256"] - assert contract["context"]["config_sha256"]["view"]["additional_fields"] is False - assert contract["context"]["calibration_sha256"]["view"]["fields"]["tactile_baseline_u16"] == "array[80]" - for name in ("config", "calibration"): - golden = cases[f"{name}_hash_golden"] - domain = bytes.fromhex(golden["domain_separator_hex"]) - view = golden["canonical_view_utf8"].encode("utf-8") - assert domain.endswith(b"\0") - assert (domain + view).hex() == golden["preimage_hex"] - assert hashlib.sha256(domain + view).hexdigest() == golden["sha256"] - assert contract["context"][f"{name}_sha256"]["golden_sha256"] == golden["sha256"] - gates = contract["tag2"]["physical_rollout_gates"] - assert gates["estimated_steady_wire_bps_max"] == 55000 - assert gates["throughput_jitter_status"] == "unqualified_until_real_hardware" - assert gates["queue_ram_cap"] == "must_freeze_before_phase5_approval" - - -def test_default_batch_timing_and_per_modality_clock_rule(): - cases = json.loads((SPEC / "cases.json").read_text())["timing"] - contract = json.loads((SPEC / "contract.json").read_text())["tag2"] - assert cases["imu_default_offsets_us"] == [0, 2000, 4000, 6000] - assert cases["mag_default_offsets_us"] == [0, 8000, 16000, 24000] - assert (cases["imu_flush_deadline_ms"], cases["mag_flush_deadline_ms"]) == (8, 32) - assert cases["global_wire_header_monotonic_required"] is False - assert cases["per_modality_reconstructed_sample_monotonic_required"] is True - assert contract["batch_flush_deadlines_ms"] == {"imu": 8, "mag": 32} - - -def test_start_ack_negotiation_is_derived_from_parsed_bytes(): - cases = json.loads((SPEC / "cases.json").read_text())["negotiation_cases"] - outcomes = {} - for case in cases: - packets, remainder, malformed = gen.parse_tag2(bytes.fromhex(case["input_hex"])) - actual = gen.admit_start_ack( - packets, - expected_nonce=gen.NONCE, - expected_contract_sha256=bytes.fromhex("55" * 32), - expected_context_sha256=gen.CONTEXT, - lifecycle=case["lifecycle"], - ) - assert actual == case["derived"] - outcomes[case["name"]] = actual - assert outcomes["start_positive"]["accepted"] is True - for name, reason in (("start_wrong_nonce", "wrong_nonce"), ("start_wrong_version", "wrong_protocol_version"), ("start_wrong_contract", "wrong_contract_sha256"), ("start_wrong_context", "wrong_context_sha256"), ("start_zero_boot", "zero_boot_id"), ("start_zero_stream", "zero_stream_id"), ("start_wrong_first_type", "no_valid_start_ack"), ("start_wrong_lifecycle", "wrong_lifecycle"), ("start_crc", "no_valid_start_ack")): - value = outcomes[name] - assert not value["accepted"] and value["fail_closed"] and value["no_tag1_fallback"] - assert value["reason"] == reason - - -def test_competing_tag2_projection_pins_canonical_contract(): - """A stacked SDK branch must not introduce a second TAG2 source of truth.""" - projection_path = ROOT / "spec" / "TAG_V2.json" - if not projection_path.exists(): - return - projection = json.loads(projection_path.read_text()) - manifest = json.loads((SPEC / "manifest.json").read_text()) - contract = json.loads((SPEC / "contract.json").read_text()) - assert projection["contract_sha256"] == manifest["contract_sha256"] - assert projection["vector_set_sha256"] == manifest["vector_set_sha256"] - assert projection["frame"]["magic_hex"] == contract["tag2"]["magic_hex"] - assert projection["frame"]["header_format"] == contract["tag2"]["header_format"] - assert projection["frame"]["header_len"] == contract["tag2"]["header_len"] - assert projection["sequence_scope"] == contract["tag2"]["sequence_scope"] - assert projection["negotiation"]["start_command"] == contract["admission"]["commands"]["start"] - assert projection["negotiation"]["start_ack_encoding"] == "typed_binary_crc" - assert "start_ack_prefix" not in projection["negotiation"] diff --git a/tools/generate_diagnostics_contract.py b/tools/generate_diagnostics_contract.py deleted file mode 100644 index 4703260..0000000 --- a/tools/generate_diagnostics_contract.py +++ /dev/null @@ -1,522 +0,0 @@ -#!/usr/bin/env python3 -"""Generate the standalone Phase-0 diagnostics contract and conformance bytes.""" -from __future__ import annotations - -import argparse -import base64 -import hashlib -import json -import re -import struct -import unicodedata -import zlib -from pathlib import Path -from typing import Any - -ROOT = Path(__file__).resolve().parents[1] -OUT = ROOT / "spec" / "diagnostics_v1" -VECTORS = OUT / "vectors" -PREFIX = b"OGLO-DIAGNOSTICS-CONTRACT-V1\0" -HEADER = struct.Struct("<2sBHIQQ") -CRC = struct.Struct(" Any: - if "\r" in raw: - raise ValueError("canonical JSON uses LF only") - return json.loads(raw, object_pairs_hook=_pairs, parse_constant=lambda x: (_ for _ in ()).throw(ValueError(f"invalid JSON constant {x}"))) - - -def canonical(value: Any) -> bytes: - def norm(v: Any) -> Any: - if v is None: - raise ValueError("null is not allowed in canonical contract/context JSON") - if isinstance(v, float): - raise ValueError("floats are not allowed in canonical contract/context JSON") - if isinstance(v, str): - if unicodedata.normalize("NFC", v) != v: - raise ValueError("strings must already be NFC") - return v - if isinstance(v, list): - return [norm(x) for x in v] - if isinstance(v, dict): - result = {} - for k, x in v.items(): - if not isinstance(k, str) or unicodedata.normalize("NFC", k) != k or k in result: - raise ValueError("keys must be unique NFC strings") - result[k] = norm(x) - return result - return v - return json.dumps(norm(value), sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode() - - -def config_calibration_golden() -> tuple[bytes, bytes, bytes, bytes]: - """Closed canonical preimages; never a native/C struct image or padding.""" - config_view={"schema":"oglo.config_view.v1","hardware_model_id":MODEL,"firmware_schema":6,"firmware_semver":{"major":0,"minor":9,"patch":12},"tag_ver_max":2,"tactile":{"taxel_count":80,"order":"finger,row,col","packing":"packed12","rate_hz":250},"imu":{"axes":["ax","ay","az","gx","gy","gz"],"raw":"i16le","rate_hz":500},"mag":{"axes":["mx","my","mz"],"raw":"i16le","rate_hz":125}} - cal_view={"schema":"oglo.calibration_view.v1","tactile_baseline_u16":[0]*80,"tactile_threshold_u16":[0]*80,"imu_bias_i16":[0]*6,"mag_bias_i16":[0]*3} - cb=canonical(config_view); kb=canonical(cal_view) - return cb,kb,hashlib.sha256(b"OGLO-CONFIG-V1\0"+cb).digest(),hashlib.sha256(b"OGLO-CALIBRATION-V1\0"+kb).digest() - - -def validate_context(raw: str) -> dict[str, Any]: - if len(raw.encode("utf-8")) > CONTEXT_MAX_UTF8: - raise ValueError("context exceeds UTF-8 byte limit") - value = strict_json(raw) - if not isinstance(value, dict) or canonical(value).decode() != raw: - raise ValueError("context must be compact sorted canonical JSON") - required = {"schema", "context_epoch", "config_sha256", "calibration_sha256"} - optional = {"capture_session_id", "recording_id", "fault_injection_id"} - if set(value) - required - optional or not required <= set(value): - raise ValueError("context has missing or unknown keys") - if value["schema"] != "oglo.device_context.v1": - raise ValueError("context schema must be oglo.device_context.v1") - if type(value["context_epoch"]) is not int or not 0 <= value["context_epoch"] <= 0xffffffff: - raise ValueError("context_epoch must be u32") - for key in ("config_sha256", "calibration_sha256"): - x = value[key] - if not isinstance(x, str) or len(x) != 64 or any(c not in "0123456789abcdef" for c in x): - raise ValueError(f"{key} must be lowercase SHA-256 hex") - if any(not isinstance(value[k], str) or not value[k] for k in optional & set(value)): - raise ValueError("context optional identifiers must be nonempty strings when present") - return value - - -def decode_context_token(token: str) -> dict[str, Any]: - """Decode the exact unpadded base64url SET CONTEXT argument.""" - if not isinstance(token, str) or not token or "=" in token or len("SET CONTEXT ".encode() + token.encode() + b"\n") > CONTEXT_COMMAND_MAX: - raise ValueError("context token must be unpadded base64url") - if any(c not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" for c in token): - raise ValueError("context token has a non-base64url character") - try: - raw = base64.urlsafe_b64decode(token + "=" * (-len(token) % 4)) - text = raw.decode("utf-8") - except (ValueError, UnicodeDecodeError) as exc: - raise ValueError("context token is not UTF-8 base64url") from exc - if base64.urlsafe_b64encode(raw).decode().rstrip("=") != token: - raise ValueError("context token is not canonical base64url") - return validate_context(text) - - -def validate_command(command: bytes) -> tuple[str, str]: - """Validate the exact LF-terminated, case-sensitive TAG2 command grammar.""" - if not isinstance(command, bytes) or not COMMAND_RE.fullmatch(command): - raise ValueError("command must be exact ASCII with one LF and no extra tokens") - text = command[:-1].decode("ascii") - if text.startswith("STREAM TAG2 ON nonce="): - return "start", text.rsplit("=", 1)[1] - if text.startswith("STREAM TAG2 OFF nonce="): - return "stop", text.rsplit("=", 1)[1] - token = text.removeprefix("SET CONTEXT ") - decode_context_token(token) - return "context", token - - -def command_transition( - lifecycle: str, - command: bytes, - *, - active_nonce: str | None = None, - retired_nonces: tuple[str, ...] = (), -) -> dict[str, Any]: - """Normative command admission model used by cross-language vectors.""" - kind, value = validate_command(command) - result = { - "lifecycle": lifecycle, - "accepted": False, - "response": "command_rejected", - "reopen": False, - } - if active_nonce is not None: - result["active_nonce"] = active_nonce - if kind == "context": - if lifecycle == "IDLE": - result.update(accepted=True, response="context_ack") - return result - if kind == "start": - if lifecycle in {"STARTING", "ACTIVE"} and value == active_nonce: - result.update(accepted=True, response="cached_start_ack") - elif lifecycle == "IDLE" and value not in retired_nonces: - result.update(lifecycle="STARTING", active_nonce=value, accepted=True, response="new_start") - elif value in retired_nonces: - result["response"] = "nonce_retired" - else: - result["response"] = "nonce_conflict" - return result - if lifecycle in {"ACTIVE", "STOPPING"} and value == active_nonce: - result.update(lifecycle="STOPPING", accepted=True, response="stop_or_cached_stop_ack") - elif value in retired_nonces: - result.update(accepted=True, response="cached_stop_ack") - else: - result["response"] = "nonce_conflict" - return result - - -def _declared_len_possible(kind: int, length: int) -> bool: - fixed = {1: 120, 128: 133, 129: 17, 130: 39, 131: 131, 132: 131, 133: 202} - if kind in fixed: - return length == fixed[kind] - if kind == 2: - return length in {1 + 14 * count for count in range(1, 9)} - if kind == 3: - return length in {1 + 8 * count for count in range(1, 9)} - return False - - -def _payload_shape_valid(kind: int, payload: bytes) -> bool: - if not _declared_len_possible(kind, len(payload)): - return False - if kind not in (2, 3): - return True - stride = 14 if kind == 2 else 8 - count = payload[0] - if not 1 <= count <= 8 or len(payload) != 1 + count * stride: - return False - offsets = [struct.unpack_from("512 or not _declared_len_possible(typ, plen) or end>len(buf): - if end>len(buf) and _declared_len_possible(typ, plen): return packets,buf[j:],bad - bad+=1;i=j+1;continue - raw=buf[j:end] - if struct.unpack_from(" str: return hashlib.sha256(data).hexdigest() -def entry(path: Path, data: bytes, provenance: str, status: str) -> dict[str, Any]: - return {"name": path.name, "owner": "oglo-python", "path": path.relative_to(ROOT).as_posix(), "provenance": provenance, "status": status, "size": len(data), "sha256": sha(data)} - - -def frame(kind: int, payload: bytes, seq: int, clock: int, stream: int) -> bytes: - head = HEADER.pack(MAGIC, kind, len(payload), seq, clock, stream) - return head + payload + CRC.pack(zlib.crc32(head + payload)) - - -def tactile() -> bytes: - values = range(500, 580); out = bytearray() - for a, b in zip(values[::2], values[1::2]): out += bytes((a >> 4, ((a & 15) << 4) | (b >> 8), b & 255)) - return bytes(out) - - -def imu_batch() -> bytes: - return bytes([4]) + b"".join(struct.pack(" bytes: - return bytes([4]) + b"".join(struct.pack(" dict[str, Any]: - config_bytes, calibration_bytes, config_hash, calibration_hash = config_calibration_golden() - f = lambda name, offset, wire_type, length, semantic, **rules: {"name": name, "offset": offset, "wire_type": wire_type, "endianness": "little" if wire_type not in ("bytes", "ascii") else "n/a", "length": length, "semantic": semantic, **rules} - header_fields = [f("magic",0,"bytes",2,"TAG2 magic A5 5B",constant="a55b"),f("type",2,"u8",1,"frame type",invalid="unknown type rejected"),f("payload_len",3,"u16",2,"payload bytes only",invalid="0..512 and exact type layout"),f("seq",5,"u32",4,"global admitted TX sequence",rule="one space per stream_id"),f("device_time_us",9,"u64",8,"data first sample/control event time"),f("stream_id",17,"u64",8,"nonzero random boot-local stream",invalid="zero rejected")] - raw_imu = [f("dt_us",0,"u16",2,"offset from header time",rule="relative to record base; first=0, later strictly increasing"), *[f(n,2+i*2,"i16",2,"raw IMU axis; relative to record base") for i,n in enumerate(("ax","ay","az","gx","gy","gz"))]] - raw_mag = [f("dt_us",0,"u16",2,"offset from header time",rule="relative to record base; first=0, later strictly increasing"), *[f(n,2+i*2,"i16",2,"raw magnetometer axis; relative to record base") for i,n in enumerate(("mx","my","mz"))]] - types = { - "1": {"name": "tactile", "payload_len": 120, "payload_format": "packed12_80_taxels", "fields": [f("taxels",0,"packed_u12_be_pairs",120,"80 counts in finger,row,col order",formula="a=b0<<4|b1>>4; b=(b1&0x0f)<<8|b2; 0..4095") ]}, - "2": {"name": "imu", "payload_format": "count:u8 + count*(dt_us:u16 + raw:<6h)", "count_range": [1, 8], "payload_len_formula": "1+14*count", "first_dt_us": 0, "later_dt_us": "strictly_increasing", "default_batch_target": 4, "fields": [f("count",0,"u8",1,"sample count",valid="1..8"), {"name":"records","offset":1,"wire_type":"array","endianness":"little","length":"14*count","stride":14,"element_offset_base":"relative_to_each_record","semantic":"IMU samples ax,ay,az,gx,gy,gz","element_fields":raw_imu}]}, - "3": {"name": "mag", "payload_format": "count:u8 + count*(dt_us:u16 + raw:<3h)", "count_range": [1, 8], "payload_len_formula": "1+8*count", "first_dt_us": 0, "later_dt_us": "strictly_increasing", "default_batch_target": 4, "fields": [f("count",0,"u8",1,"sample count",valid="1..8"), {"name":"records","offset":1,"wire_type":"array","endianness":"little","length":"8*count","stride":8,"element_offset_base":"relative_to_each_record","semantic":"magnetometer samples mx,my,mz","element_fields":raw_mag}]}, - "128": {"name": "START_ACK", "payload_format": "=2 selects min(host_max,device_max); malformed_or_contradictory_fails_closed", "command_encoding":"strict ASCII, exact uppercase and single spaces, one LF; CRLF/leading/trailing/extra tokens rejected", "commands": {"start": "STREAM TAG2 ON nonce=<32 lowercase hex>\n", "stop": "STREAM TAG2 OFF nonce=\n", "context":"SET CONTEXT \n"}, "start": "first valid frame after ON is START_ACK matching nonce, stream_id, contract_sha256; preserve fragment/coalesced tail; invalid ACK/CRC/wrong stream never downgrades", "active": "same START nonce retry returns byte-identical cached ACK/current stream; different nonce gives nonfatal nonce_conflict/no transition; only matching STOP accepted; other commands/context mutation nonfatal command_rejected; context/topology mutation uses fatal terminal order", "stop": "same STOP retry returns cached STOP_ACK within boot; retired START nonce cannot reopen; new nonce after terminal gets new stream_id", "host": "CRC-valid wrong-stream frame is stale evidence: discard and continue without changing current expected seq/time/counters; never TAG1 fallback", "receiver_sequence": "expected seq modulo 2^32; exact accepts; forward distance <2^31 records gap then accepts; backward distance is duplicate/out-of-order; cached control replay neither advances nor gaps", "parser_resync": "scan negotiated magic only; retain incomplete frame exactly and one possible magic-prefix byte when no complete magic; invalid type/length/CRC advances scan by one byte after the first magic byte; never reinterpret TAG2 as TAG1"}, - "context": {"schema": "oglo.device_context.v1", "required": ["schema", "context_epoch", "config_sha256", "calibration_sha256"], "optional_omit_when_unknown": ["capture_session_id", "recording_id", "fault_injection_id"], "max_utf8_bytes": CONTEXT_MAX_UTF8, "max_base64url_bytes": 1366, "max_set_context_command_bytes": CONTEXT_COMMAND_MAX, "rules": "no_nulls; strings_NFC; hashes_lowercase_64_hex; u32_context_epoch_host_published_only; UTF-8_sorted_keys_compact_separators", "context_sha256": {"algorithm":"SHA256","preimage":"canonical UTF-8 bytes without LF"}, "config_sha256": {"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":b"OGLO-CONFIG-V1\0".hex(),"golden_sha256":config_hash.hex(),"view":{"schema":"oglo.config_view.v1","additional_fields":False,"fields":{"hardware_model_id":"u32","firmware_schema":"u8","firmware_semver":"{major:u8,minor:u8,patch:u8}","tag_ver_max":"u8","tactile":{"taxel_count":"80","order":"finger,row,col","packing":"packed12","rate_hz":"u16"},"imu":{"axes":"array[ax,ay,az,gx,gy,gz]","raw":"i16le","rate_hz":"u16"},"mag":{"axes":"array[mx,my,mz]","raw":"i16le","rate_hz":"u16"}}}}, "calibration_sha256": {"algorithm":"SHA256","construction":"domain_separator_bytes || canonical_view_utf8","domain_separator_hex":b"OGLO-CALIBRATION-V1\0".hex(),"golden_sha256":calibration_hash.hex(),"view":{"schema":"oglo.calibration_view.v1","additional_fields":False,"fields":{"tactile_baseline_u16":"array[80]","tactile_threshold_u16":"array[80]","imu_bias_i16":"array[6]","mag_bias_i16":"array[3]"}}}, "epoch_ownership":{"config_epoch":"firmware: starts 1 after boot NVS load; increments once per successful config or calibration mutation outside ACTIVE","context_epoch":"host: supplied only by SET CONTEXT and independent of config_epoch"}, "set_command": "INACTIVE-only exact ASCII SET CONTEXT \\n", "ack": "#CONTEXT ok sha256=<64 lowercase hex>\\n", "rejection": "#CONTEXT error code=command_rejected\\n", "writer": "single_TX_writer_when_implemented", "active": "ACTIVE rejects mutation through typed STREAM_FAILURE command_rejected", "evidence": "ACK hash must equal START_ACK and DEVICE_CONTEXT context_sha256; mismatch is typed fatal termination/no TAG1 fallback"}, - "canonicalization": {"json": "UTF-8,NFC,LF,object_keys_sorted,compact_separators,no_duplicate_keys,no_null,no_NaN,no_float,unknown_optional_omitted; runtime_u64_and_ns_use_decimal_strings_when_JSON", "hash_preimage": "SHA256(b'OGLO-DIAGNOSTICS-CONTRACT-V1\\0' + canonical_json(manifest.contract_inputs))", "generated_vector_hashes": "inventory only; excluded from contract_sha256 preimage to avoid START_ACK self-reference"}} - - -def cases() -> dict[str, Any]: - config_bytes, calibration_bytes, config_hash, calibration_hash = config_calibration_golden() - valid = {"calibration_sha256": "3" * 64, "config_sha256": "2" * 64, "context_epoch": 7, "schema": "oglo.device_context.v1"} - raw = canonical(valid).decode() - token = base64.urlsafe_b64encode(raw.encode()).decode().rstrip("=") - max_value = {**valid, "recording_id": ""} - padding = CONTEXT_MAX_UTF8 - len(canonical(max_value)) - max_value["recording_id"] = "x" * padding - max_raw = canonical(max_value).decode() - oversize_value = {**max_value, "recording_id": max_value["recording_id"] + "x"} - oversize_raw = canonical(oversize_value).decode() - start_payload = struct.pack(" dict[str, Any]: - packets, remainder, malformed = parse_tag2(data) - state = admit(packets, STREAM, expected_seq, cached) - return { - "name": name, - "input_hex": data.hex(), - "initial_expected_seq": expected_seq, - "expected_packets": [ - {"type": packet["type"], "seq": packet["seq"], "stream_id": str(packet["stream_id"])} - for packet in packets - ], - "expected_remainder_hex": remainder.hex(), - "expected_malformed": malformed, - "expected_admission": state, - } - - corrupted_data = bytearray(tactile_frame) - corrupted_data[-1] ^= 1 - corrupted_ack = bytearray(start) - corrupted_ack[-1] ^= 1 - wrong_stream = frame(1, tactile(), 0, (2 << 32) + 4, OTHER_STREAM) - unknown = frame(99, b"", 0, (2 << 32) + 5, STREAM) - bad_length = frame(1, b"\0", 0, (2 << 32) + 6, STREAM) - sequence_cases = [ - parser_case("normal", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 1, 11, STREAM)), - parser_case("forward_gap", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 2, 12, STREAM)), - parser_case("duplicate", frame(1, tactile(), 0, 10, STREAM) + frame(1, tactile(), 0, 10, STREAM)), - parser_case("out_of_order", frame(1, tactile(), 2, 10, STREAM), expected_seq=3), - parser_case("wrap", frame(1, tactile(), 0xFFFFFFFE, 10, STREAM) + frame(1, tactile(), 0xFFFFFFFF, 11, STREAM) + frame(1, tactile(), 0, 12, STREAM), expected_seq=0xFFFFFFFE), - parser_case("cached_start_ack", start, expected_seq=1, cached=((128, 0, start_payload),)), - ] - parser_cases = [ - parser_case("valid_data", tactile_frame, expected_seq=2), - parser_case("crc_data_reject_continue", bytes(corrupted_data), expected_seq=2), - parser_case("crc_start_ack_malformed", bytes(corrupted_ack)), - parser_case("wrong_stream_stale", wrong_stream), - parser_case("unknown_type", unknown), - parser_case("bad_type_length", bad_length), - parser_case("coalesced_start_boot", start + boot), - parser_case("coalesced_start_partial_boot", start + boot[:40]), - *[parser_case(f"split_start_{length}", start[:length]) for length in (1, 24, 25, 100)], - *sequence_cases, - ] - nonce_hex = NONCE.hex() - other_nonce = bytes.fromhex("ffeeddccbbaa99887766554433221100").hex() - valid_commands = [ - f"STREAM TAG2 ON nonce={nonce_hex}\n".encode(), - f"STREAM TAG2 OFF nonce={nonce_hex}\n".encode(), - f"SET CONTEXT {token}\n".encode(), - ] - transition_inputs = [ - ("new_start", "IDLE", valid_commands[0], None, ()), - ("same_start_active", "ACTIVE", valid_commands[0], nonce_hex, ()), - ("different_start_active", "ACTIVE", f"STREAM TAG2 ON nonce={other_nonce}\n".encode(), nonce_hex, ()), - ("matching_stop", "ACTIVE", valid_commands[1], nonce_hex, ()), - ("retired_start", "IDLE", valid_commands[0], None, (nonce_hex,)), - ("cached_stop", "IDLE", valid_commands[1], None, (nonce_hex,)), - ("context_idle", "IDLE", valid_commands[2], None, ()), - ("context_active", "ACTIVE", valid_commands[2], nonce_hex, ()), - ] - invalid_commands = [ - valid_commands[0][:-1], - valid_commands[0][:-1] + b"\r\n", - valid_commands[0].replace(b"STREAM", b"stream"), - valid_commands[0].replace(b"TAG2 ON", b"TAG2 ON"), - valid_commands[0].replace(nonce_hex.encode(), nonce_hex.upper().encode()), - valid_commands[0][:-1] + b" extra\n", - ] - def start_case(name, wire, lifecycle="STARTING"): - packets, tail, malformed = parse_tag2(wire) - return {"name":name,"input_hex":wire.hex(),"lifecycle":lifecycle,"malformed":malformed,"remainder_hex":tail.hex(),"derived":admit_start_ack(packets,expected_nonce=NONCE,expected_contract_sha256=bytes.fromhex("55"*32),expected_context_sha256=CONTEXT,lifecycle=lifecycle)} - def ack_variant(*, version=2, nonce=NONCE, boot_id=BOOT, contract=bytes.fromhex("55"*32), context=CONTEXT, stream=STREAM, kind=128): - payload=struct.pack(">4),((payload[j+1]&15)<<8)|payload[j+2]] - base["payload"]={"taxels":vals,"order":"finger,row,col"} - elif kind in (2,3): - stride=14 if kind==2 else 8; names=("ax","ay","az","gx","gy","gz") if kind==2 else ("mx","my","mz") - records=[] - for j in range(payload[0]): - values=struct.unpack_from(" dict[Path, bytes]: - c = contract(); cs = cases(); result = {OUT/"contract.json": canonical(c)+b"\n", OUT/"cases.json": canonical(cs)+b"\n"} - physical = [ROOT/"spec/vectors/tag_tactile_133b.bin", ROOT/"spec/vectors/tag_imu_25b.bin", ROOT/"spec/vectors/tag_mag_19b.bin"] - contract_entries = [entry(OUT/"contract.json",result[OUT/"contract.json"],"synthetic","implementation-contract-not-hardware-captured")] + [entry(p,p.read_bytes(),"physical_capture","existing_TAG1_capture") for p in physical] - contract_entries.sort(key=lambda x:x["name"]) - cases_entry = entry(OUT/"cases.json",result[OUT/"cases.json"],"synthetic","implementation-contract-not-hardware-captured") - inputs = {"schema":"oglo.diagnostics_manifest.v1","status":"implementation-contract-not-hardware-captured","canonicalization":c["canonicalization"],"contract_entries":contract_entries,"cases_entry":cases_entry} - h = sha(PREFIX+canonical(inputs)); - for r in records(bytes.fromhex(h)): - result[VECTORS/(r["name"]+".bin")] = r["frame"] - result[VECTORS/(r["name"]+".expected.json")] = canonical({k:v for k,v in r.items() if k != "frame"})+b"\n" - generated = [entry(p,d,"synthetic","implementation-contract-not-hardware-captured") for p,d in result.items() if p.parent == VECTORS] - generated.sort(key=lambda x:x["name"]) - result[OUT/"manifest.json"] = canonical({"contract_inputs":inputs,"contract_sha256":h,"vector_set_sha256":sha(canonical(generated)),"generated_vector_entries":generated,"conformance_pins":"Python/C++/TypeScript pin both contract_sha256 and vector_set_sha256"})+b"\n" - return result - - -def main() -> int: - check = argparse.ArgumentParser(); check.add_argument("--check",action="store_true"); args=check.parse_args(); wanted=files() - stale = sorted(p for p in VECTORS.glob("*") if p.is_file() and p not in wanted) - bad = [p for p,d in wanted.items() if not p.exists() or p.read_bytes()!=d] + stale - if args.check: - if bad: print("out of date: "+", ".join(p.relative_to(ROOT).as_posix() for p in bad)); return 1 - print(f"verified {len(wanted)} diagnostics contract artifacts"); return 0 - for p,d in wanted.items(): p.parent.mkdir(parents=True,exist_ok=True); p.write_bytes(d) - for p in stale: p.unlink() - print(f"generated {len(wanted)} diagnostics contract artifacts"); return 0 - -if __name__ == "__main__": raise SystemExit(main())