diff --git a/examples/captures/demo_new/capture-artifact-manifest.json b/examples/captures/demo_new/capture-artifact-manifest.json index 0faafff..c4bcf7e 100644 --- a/examples/captures/demo_new/capture-artifact-manifest.json +++ b/examples/captures/demo_new/capture-artifact-manifest.json @@ -1 +1 @@ -{"artifacts":[{"path":"capture-state.json","sha256":"b96ed12c609334c76636598b336d664557d444a7060df2bdace4e25f7b368cfa","size_bytes":370},{"path":"recording.db","sha256":"24fa103e41c6c6d15b423fd7a3264f82df026d1ca1c449551becd2b49735aca2","size_bytes":249856},{"path":"synthetic-provenance.json","sha256":"5a3fb539cb3a702b310b53ac25bfb157a63dfc2f14bd3d645ac2d572e1b8fda5","size_bytes":3112}],"schema_version":"openadapt.capture-artifact-manifest/v1"} +{"artifacts":[{"path":"capture-state.json","sha256":"b96ed12c609334c76636598b336d664557d444a7060df2bdace4e25f7b368cfa","size_bytes":370},{"path":"recording.db","sha256":"bcd9d62065ab9bfca346a43a0fd347a6321dd2c2de69e44ef8e9e2475afa65b9","size_bytes":245760},{"path":"synthetic-provenance.json","sha256":"bd76448dfe409e02a51e7c23aaee75a7b74cebc020895bbd1eb47523c90ecd1b","size_bytes":3009}],"schema_version":"openadapt.capture-artifact-manifest/v1"} diff --git a/examples/captures/demo_new/capture-terminal.json b/examples/captures/demo_new/capture-terminal.json index f7c8b01..29968d3 100644 --- a/examples/captures/demo_new/capture-terminal.json +++ b/examples/captures/demo_new/capture-terminal.json @@ -1 +1 @@ -{"artifact_manifest_sha256":"03980cfda7efa77aca53d2890e70362712627d30563f387f42b82cae88634e93","artifact_manifest_size_bytes":448,"ended_at":"2026-01-01T00:00:13.000000Z","event_counts":{"action":14,"browser":0,"screen":14,"video":0,"window":0},"last_source_ordinal":28,"reason_code":"normal_stop","schema_version":"openadapt.capture-terminal/v2","source_capture_session_sha256":"1d64c69bbd4b28e980830c4dc446048b51fe9c6897f225870a2b9b2a7bfc0261","started_at":"2026-01-01T00:00:00.000000Z","state":"COMPLETE","terminal_sha256":"0b7ce6eed73b6b3e1a2a45cc77996775fb5ea2c1d40f15106ce43cd1c2c77855"} +{"artifact_manifest_sha256":"0b90d55a85b57b8d57d0ac5bcc0730553fee5f79027162a7b71d1b9dd492ce30","artifact_manifest_size_bytes":448,"ended_at":"2026-01-01T00:00:13.000000Z","event_counts":{"action":14,"browser":0,"screen":14,"video":0,"window":0},"last_source_ordinal":28,"reason_code":"normal_stop","schema_version":"openadapt.capture-terminal/v2","source_capture_session_sha256":"1d64c69bbd4b28e980830c4dc446048b51fe9c6897f225870a2b9b2a7bfc0261","started_at":"2026-01-01T00:00:00.000000Z","state":"COMPLETE","terminal_sha256":"a25eb0467508e2d36b9783d149d91a19640d818161b389635379e2183db11037"} diff --git a/examples/captures/demo_new/recording.db b/examples/captures/demo_new/recording.db index 52805bc..c967a41 100644 Binary files a/examples/captures/demo_new/recording.db and b/examples/captures/demo_new/recording.db differ diff --git a/examples/captures/demo_new/synthetic-provenance.json b/examples/captures/demo_new/synthetic-provenance.json index 709e012..0179f8f 100644 --- a/examples/captures/demo_new/synthetic-provenance.json +++ b/examples/captures/demo_new/synthetic-provenance.json @@ -1 +1 @@ -{"action_bindings":[{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open workspace","source_ordinal":2,"timestamp_us":1767225600250000},{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open launcher","source_ordinal":3,"timestamp_us":1767225600750000},{"after_frame_source_ordinal":6,"before_frame_source_ordinal":4,"label":"Select calculator","source_ordinal":5,"timestamp_us":1767225601500000},{"after_frame_source_ordinal":8,"before_frame_source_ordinal":6,"label":"Focus keypad","source_ordinal":7,"timestamp_us":1767225602500000},{"after_frame_source_ordinal":10,"before_frame_source_ordinal":8,"label":"Enter two","source_ordinal":9,"timestamp_us":1767225603500000},{"after_frame_source_ordinal":12,"before_frame_source_ordinal":10,"label":"Choose multiply","source_ordinal":11,"timestamp_us":1767225604500000},{"after_frame_source_ordinal":14,"before_frame_source_ordinal":12,"label":"Enter three","source_ordinal":13,"timestamp_us":1767225605500000},{"after_frame_source_ordinal":16,"before_frame_source_ordinal":14,"label":"Compute result","source_ordinal":15,"timestamp_us":1767225606500000},{"after_frame_source_ordinal":18,"before_frame_source_ordinal":16,"label":"Clear display","source_ordinal":17,"timestamp_us":1767225607500000},{"after_frame_source_ordinal":20,"before_frame_source_ordinal":18,"label":"Open history","source_ordinal":19,"timestamp_us":1767225608500000},{"after_frame_source_ordinal":22,"before_frame_source_ordinal":20,"label":"Select result","source_ordinal":21,"timestamp_us":1767225609500000},{"after_frame_source_ordinal":24,"before_frame_source_ordinal":22,"label":"Pin result","source_ordinal":23,"timestamp_us":1767225610500000},{"after_frame_source_ordinal":26,"before_frame_source_ordinal":24,"label":"Close history","source_ordinal":25,"timestamp_us":1767225611500000},{"after_frame_source_ordinal":28,"before_frame_source_ordinal":26,"label":"Finish demo","source_ordinal":27,"timestamp_us":1767225612500000}],"builder":{"database_normalization":"vacuum_4096_writer_version_zero","lock_path":"uv.lock","lock_sha256":"fb9a77ab8787cbdf178c2282113908d1790cd54da63130020d0efa51d844104c","sqlalchemy_version":"2.0.52","sqlite_version":"3.53.1"},"contains_human_recording":false,"contains_personal_data":false,"fixture_id":"demo_new","generator":{"path":"scripts/generate_synthetic_captures.py","sha256":"1a8783dbe194529456861a4032e4ea9054caacc781f5fe527f761cd405f82d75"},"purpose":"public_documentation","qualification_eligible":false,"schema_version":"openadapt.capture.synthetic-fixture-provenance/v1","source":{"path":"examples/captures/specs/demo_new.json","sha256":"38b8db9f6fcc8eaf6f6d267edb68f634d9e8cef9c67bf8d1c8f3ecbd7cf44a20"},"synthetic":true,"timing":{"frame_interval_us":1000000,"frame_timestamps_us":[1767225600000000,1767225601000000,1767225602000000,1767225603000000,1767225604000000,1767225605000000,1767225606000000,1767225607000000,1767225608000000,1767225609000000,1767225610000000,1767225611000000,1767225612000000,1767225613000000],"kind":"declared_synthetic","start_timestamp_us":1767225600000000}} +{"action_bindings":[{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open workspace","source_ordinal":2,"timestamp_us":1767225600250000},{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open launcher","source_ordinal":3,"timestamp_us":1767225600750000},{"after_frame_source_ordinal":6,"before_frame_source_ordinal":4,"label":"Select calculator","source_ordinal":5,"timestamp_us":1767225601500000},{"after_frame_source_ordinal":8,"before_frame_source_ordinal":6,"label":"Focus keypad","source_ordinal":7,"timestamp_us":1767225602500000},{"after_frame_source_ordinal":10,"before_frame_source_ordinal":8,"label":"Enter two","source_ordinal":9,"timestamp_us":1767225603500000},{"after_frame_source_ordinal":12,"before_frame_source_ordinal":10,"label":"Choose multiply","source_ordinal":11,"timestamp_us":1767225604500000},{"after_frame_source_ordinal":14,"before_frame_source_ordinal":12,"label":"Enter three","source_ordinal":13,"timestamp_us":1767225605500000},{"after_frame_source_ordinal":16,"before_frame_source_ordinal":14,"label":"Compute result","source_ordinal":15,"timestamp_us":1767225606500000},{"after_frame_source_ordinal":18,"before_frame_source_ordinal":16,"label":"Clear display","source_ordinal":17,"timestamp_us":1767225607500000},{"after_frame_source_ordinal":20,"before_frame_source_ordinal":18,"label":"Open history","source_ordinal":19,"timestamp_us":1767225608500000},{"after_frame_source_ordinal":22,"before_frame_source_ordinal":20,"label":"Select result","source_ordinal":21,"timestamp_us":1767225609500000},{"after_frame_source_ordinal":24,"before_frame_source_ordinal":22,"label":"Pin result","source_ordinal":23,"timestamp_us":1767225610500000},{"after_frame_source_ordinal":26,"before_frame_source_ordinal":24,"label":"Close history","source_ordinal":25,"timestamp_us":1767225611500000},{"after_frame_source_ordinal":28,"before_frame_source_ordinal":26,"label":"Finish demo","source_ordinal":27,"timestamp_us":1767225612500000}],"builder":{"database_normalization":"vacuum_4096_writer_version_zero","sqlalchemy_version":"2.0.52","sqlite_version":"3.53.1"},"contains_human_recording":false,"contains_personal_data":false,"fixture_id":"demo_new","generator":{"path":"scripts/generate_synthetic_captures.py","sha256":"e2649b56917279ca5d53d3412cef36d6a096a1cb094ef4bc7cd7b0a2826562a4"},"purpose":"public_documentation","qualification_eligible":false,"schema_version":"openadapt.capture.synthetic-fixture-provenance/v2","source":{"path":"examples/captures/specs/demo_new.json","sha256":"38b8db9f6fcc8eaf6f6d267edb68f634d9e8cef9c67bf8d1c8f3ecbd7cf44a20"},"synthetic":true,"timing":{"frame_interval_us":1000000,"frame_timestamps_us":[1767225600000000,1767225601000000,1767225602000000,1767225603000000,1767225604000000,1767225605000000,1767225606000000,1767225607000000,1767225608000000,1767225609000000,1767225610000000,1767225611000000,1767225612000000,1767225613000000],"kind":"declared_synthetic","start_timestamp_us":1767225600000000}} diff --git a/examples/captures/turn-off-nightshift/capture-artifact-manifest.json b/examples/captures/turn-off-nightshift/capture-artifact-manifest.json index 0cc5656..b00e7db 100644 --- a/examples/captures/turn-off-nightshift/capture-artifact-manifest.json +++ b/examples/captures/turn-off-nightshift/capture-artifact-manifest.json @@ -1 +1 @@ -{"artifacts":[{"path":"capture-state.json","sha256":"9e8fbfff339fdf6a02fb8c84856695fbacb46fa5c914e1e261e6b2087fdcddf3","size_bytes":381},{"path":"recording.db","sha256":"3ab3a463b62190e4d3050b661327e798a24ecb64af268c99fa0f8c71fef29f51","size_bytes":323584},{"path":"synthetic-provenance.json","sha256":"6ef2a6d4c811f8c3de16fc4125462e5aa1a6becf8440454b7b69a5eeb4e35679","size_bytes":4128}],"schema_version":"openadapt.capture-artifact-manifest/v1"} +{"artifacts":[{"path":"capture-state.json","sha256":"9e8fbfff339fdf6a02fb8c84856695fbacb46fa5c914e1e261e6b2087fdcddf3","size_bytes":381},{"path":"recording.db","sha256":"cd43740bb5b1af798c570070543293340a83321cc3308f4cb186a4e63270d933","size_bytes":323584},{"path":"synthetic-provenance.json","sha256":"758ee9965e6b13e1e0d4c8da5ae68b9049bfa9c296fa88052a532b9bd5f14e35","size_bytes":4025}],"schema_version":"openadapt.capture-artifact-manifest/v1"} diff --git a/examples/captures/turn-off-nightshift/capture-terminal.json b/examples/captures/turn-off-nightshift/capture-terminal.json index 802f006..3562fdb 100644 --- a/examples/captures/turn-off-nightshift/capture-terminal.json +++ b/examples/captures/turn-off-nightshift/capture-terminal.json @@ -1 +1 @@ -{"artifact_manifest_sha256":"b4bb5d3f2c052c0becd540fbf040f59194ee409edd0b68957a809cc78e18ab0b","artifact_manifest_size_bytes":448,"ended_at":"2026-01-01T01:00:19.000000Z","event_counts":{"action":20,"browser":0,"screen":20,"video":0,"window":0},"last_source_ordinal":40,"reason_code":"normal_stop","schema_version":"openadapt.capture-terminal/v2","source_capture_session_sha256":"1aae77c64032ae92b73e1feae36fa6de7bc972830389a856e8fa14c0a93e9380","started_at":"2026-01-01T01:00:00.000000Z","state":"COMPLETE","terminal_sha256":"710a094994a1383ac042e058657003b6fabbeb6f9505729e5edc2daf35f3e6ad"} +{"artifact_manifest_sha256":"db39196790056643bba5d63af25670c8b05206add3b1bd1c1c822728fa9b6d98","artifact_manifest_size_bytes":448,"ended_at":"2026-01-01T01:00:19.000000Z","event_counts":{"action":20,"browser":0,"screen":20,"video":0,"window":0},"last_source_ordinal":40,"reason_code":"normal_stop","schema_version":"openadapt.capture-terminal/v2","source_capture_session_sha256":"1aae77c64032ae92b73e1feae36fa6de7bc972830389a856e8fa14c0a93e9380","started_at":"2026-01-01T01:00:00.000000Z","state":"COMPLETE","terminal_sha256":"aa3fab7e8cb2b7f3361403360d9e57d0dc0585d6ac4ff016ad743e9f4b78fb54"} diff --git a/examples/captures/turn-off-nightshift/recording.db b/examples/captures/turn-off-nightshift/recording.db index c02635b..ea78d3c 100644 Binary files a/examples/captures/turn-off-nightshift/recording.db and b/examples/captures/turn-off-nightshift/recording.db differ diff --git a/examples/captures/turn-off-nightshift/synthetic-provenance.json b/examples/captures/turn-off-nightshift/synthetic-provenance.json index 698548b..497e3e2 100644 --- a/examples/captures/turn-off-nightshift/synthetic-provenance.json +++ b/examples/captures/turn-off-nightshift/synthetic-provenance.json @@ -1 +1 @@ -{"action_bindings":[{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open settings","source_ordinal":2,"timestamp_us":1767229200250000},{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open display panel","source_ordinal":3,"timestamp_us":1767229200750000},{"after_frame_source_ordinal":6,"before_frame_source_ordinal":4,"label":"Select display","source_ordinal":5,"timestamp_us":1767229201500000},{"after_frame_source_ordinal":8,"before_frame_source_ordinal":6,"label":"Open color options","source_ordinal":7,"timestamp_us":1767229202500000},{"after_frame_source_ordinal":10,"before_frame_source_ordinal":8,"label":"Review schedule","source_ordinal":9,"timestamp_us":1767229203500000},{"after_frame_source_ordinal":12,"before_frame_source_ordinal":10,"label":"Select warm display","source_ordinal":11,"timestamp_us":1767229204500000},{"after_frame_source_ordinal":14,"before_frame_source_ordinal":12,"label":"Open schedule","source_ordinal":13,"timestamp_us":1767229205500000},{"after_frame_source_ordinal":16,"before_frame_source_ordinal":14,"label":"Choose manual","source_ordinal":15,"timestamp_us":1767229206500000},{"after_frame_source_ordinal":18,"before_frame_source_ordinal":16,"label":"Confirm schedule","source_ordinal":17,"timestamp_us":1767229207500000},{"after_frame_source_ordinal":20,"before_frame_source_ordinal":18,"label":"Open status","source_ordinal":19,"timestamp_us":1767229208500000},{"after_frame_source_ordinal":22,"before_frame_source_ordinal":20,"label":"Select toggle","source_ordinal":21,"timestamp_us":1767229209500000},{"after_frame_source_ordinal":24,"before_frame_source_ordinal":22,"label":"Turn setting off","source_ordinal":23,"timestamp_us":1767229210500000},{"after_frame_source_ordinal":26,"before_frame_source_ordinal":24,"label":"Confirm off state","source_ordinal":25,"timestamp_us":1767229211500000},{"after_frame_source_ordinal":28,"before_frame_source_ordinal":26,"label":"Review display","source_ordinal":27,"timestamp_us":1767229212500000},{"after_frame_source_ordinal":30,"before_frame_source_ordinal":28,"label":"Open details","source_ordinal":29,"timestamp_us":1767229213500000},{"after_frame_source_ordinal":32,"before_frame_source_ordinal":30,"label":"Close details","source_ordinal":31,"timestamp_us":1767229214500000},{"after_frame_source_ordinal":34,"before_frame_source_ordinal":32,"label":"Review summary","source_ordinal":33,"timestamp_us":1767229215500000},{"after_frame_source_ordinal":36,"before_frame_source_ordinal":34,"label":"Confirm summary","source_ordinal":35,"timestamp_us":1767229216500000},{"after_frame_source_ordinal":38,"before_frame_source_ordinal":36,"label":"Close settings","source_ordinal":37,"timestamp_us":1767229217500000},{"after_frame_source_ordinal":40,"before_frame_source_ordinal":38,"label":"Finish demo","source_ordinal":39,"timestamp_us":1767229218500000}],"builder":{"database_normalization":"vacuum_4096_writer_version_zero","lock_path":"uv.lock","lock_sha256":"fb9a77ab8787cbdf178c2282113908d1790cd54da63130020d0efa51d844104c","sqlalchemy_version":"2.0.52","sqlite_version":"3.53.1"},"contains_human_recording":false,"contains_personal_data":false,"fixture_id":"turn-off-nightshift","generator":{"path":"scripts/generate_synthetic_captures.py","sha256":"1a8783dbe194529456861a4032e4ea9054caacc781f5fe527f761cd405f82d75"},"purpose":"public_documentation","qualification_eligible":false,"schema_version":"openadapt.capture.synthetic-fixture-provenance/v1","source":{"path":"examples/captures/specs/turn-off-nightshift.json","sha256":"1b52ca817849c47be867e1b14423890860484daf7c5dacbafcbea28fa4a37cbe"},"synthetic":true,"timing":{"frame_interval_us":1000000,"frame_timestamps_us":[1767229200000000,1767229201000000,1767229202000000,1767229203000000,1767229204000000,1767229205000000,1767229206000000,1767229207000000,1767229208000000,1767229209000000,1767229210000000,1767229211000000,1767229212000000,1767229213000000,1767229214000000,1767229215000000,1767229216000000,1767229217000000,1767229218000000,1767229219000000],"kind":"declared_synthetic","start_timestamp_us":1767229200000000}} +{"action_bindings":[{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open settings","source_ordinal":2,"timestamp_us":1767229200250000},{"after_frame_source_ordinal":4,"before_frame_source_ordinal":1,"label":"Open display panel","source_ordinal":3,"timestamp_us":1767229200750000},{"after_frame_source_ordinal":6,"before_frame_source_ordinal":4,"label":"Select display","source_ordinal":5,"timestamp_us":1767229201500000},{"after_frame_source_ordinal":8,"before_frame_source_ordinal":6,"label":"Open color options","source_ordinal":7,"timestamp_us":1767229202500000},{"after_frame_source_ordinal":10,"before_frame_source_ordinal":8,"label":"Review schedule","source_ordinal":9,"timestamp_us":1767229203500000},{"after_frame_source_ordinal":12,"before_frame_source_ordinal":10,"label":"Select warm display","source_ordinal":11,"timestamp_us":1767229204500000},{"after_frame_source_ordinal":14,"before_frame_source_ordinal":12,"label":"Open schedule","source_ordinal":13,"timestamp_us":1767229205500000},{"after_frame_source_ordinal":16,"before_frame_source_ordinal":14,"label":"Choose manual","source_ordinal":15,"timestamp_us":1767229206500000},{"after_frame_source_ordinal":18,"before_frame_source_ordinal":16,"label":"Confirm schedule","source_ordinal":17,"timestamp_us":1767229207500000},{"after_frame_source_ordinal":20,"before_frame_source_ordinal":18,"label":"Open status","source_ordinal":19,"timestamp_us":1767229208500000},{"after_frame_source_ordinal":22,"before_frame_source_ordinal":20,"label":"Select toggle","source_ordinal":21,"timestamp_us":1767229209500000},{"after_frame_source_ordinal":24,"before_frame_source_ordinal":22,"label":"Turn setting off","source_ordinal":23,"timestamp_us":1767229210500000},{"after_frame_source_ordinal":26,"before_frame_source_ordinal":24,"label":"Confirm off state","source_ordinal":25,"timestamp_us":1767229211500000},{"after_frame_source_ordinal":28,"before_frame_source_ordinal":26,"label":"Review display","source_ordinal":27,"timestamp_us":1767229212500000},{"after_frame_source_ordinal":30,"before_frame_source_ordinal":28,"label":"Open details","source_ordinal":29,"timestamp_us":1767229213500000},{"after_frame_source_ordinal":32,"before_frame_source_ordinal":30,"label":"Close details","source_ordinal":31,"timestamp_us":1767229214500000},{"after_frame_source_ordinal":34,"before_frame_source_ordinal":32,"label":"Review summary","source_ordinal":33,"timestamp_us":1767229215500000},{"after_frame_source_ordinal":36,"before_frame_source_ordinal":34,"label":"Confirm summary","source_ordinal":35,"timestamp_us":1767229216500000},{"after_frame_source_ordinal":38,"before_frame_source_ordinal":36,"label":"Close settings","source_ordinal":37,"timestamp_us":1767229217500000},{"after_frame_source_ordinal":40,"before_frame_source_ordinal":38,"label":"Finish demo","source_ordinal":39,"timestamp_us":1767229218500000}],"builder":{"database_normalization":"vacuum_4096_writer_version_zero","sqlalchemy_version":"2.0.52","sqlite_version":"3.53.1"},"contains_human_recording":false,"contains_personal_data":false,"fixture_id":"turn-off-nightshift","generator":{"path":"scripts/generate_synthetic_captures.py","sha256":"e2649b56917279ca5d53d3412cef36d6a096a1cb094ef4bc7cd7b0a2826562a4"},"purpose":"public_documentation","qualification_eligible":false,"schema_version":"openadapt.capture.synthetic-fixture-provenance/v2","source":{"path":"examples/captures/specs/turn-off-nightshift.json","sha256":"1b52ca817849c47be867e1b14423890860484daf7c5dacbafcbea28fa4a37cbe"},"synthetic":true,"timing":{"frame_interval_us":1000000,"frame_timestamps_us":[1767229200000000,1767229201000000,1767229202000000,1767229203000000,1767229204000000,1767229205000000,1767229206000000,1767229207000000,1767229208000000,1767229209000000,1767229210000000,1767229211000000,1767229212000000,1767229213000000,1767229214000000,1767229215000000,1767229216000000,1767229217000000,1767229218000000,1767229219000000],"kind":"declared_synthetic","start_timestamp_us":1767229200000000}} diff --git a/scripts/generate_synthetic_captures.py b/scripts/generate_synthetic_captures.py index 67a92ff..bc91ddd 100644 --- a/scripts/generate_synthetic_captures.py +++ b/scripts/generate_synthetic_captures.py @@ -25,7 +25,7 @@ from openadapt_capture.terminal import seal_capture SOURCE_SCHEMA = "openadapt.capture.synthetic-fixture-source/v1" -PROVENANCE_SCHEMA = "openadapt.capture.synthetic-fixture-provenance/v1" +PROVENANCE_SCHEMA = "openadapt.capture.synthetic-fixture-provenance/v2" FIXTURE_SCHEMA = "openadapt.capture.synthetic-fixture/v1" GENERATED_FILENAMES = { "capture-artifact-manifest.json", @@ -36,7 +36,11 @@ } VIEWPORT = (960, 540) FRAME_INTERVAL_US = 1_000_000 -BUILDER_LOCK_PATH = "uv.lock" +# The committed fixture bytes are produced by the source spec, this generator, +# the repository's own writer code, and the two library versions below. +# `require_exact_builder` refuses any other writer, and +# `check_generated` regenerates every artifact and compares it byte for byte, +# so a change to any determinant fails the fixture suite. REQUIRED_SQLALCHEMY_VERSION = "2.0.52" REQUIRED_SQLITE_VERSION = "3.53.1" @@ -433,9 +437,6 @@ def generate_fixture( _remove_previous_generated_files(destination) source_sha256 = sha256_bytes(spec_raw) generator_sha256 = sha256_file(generator_path) - builder_lock = generator_path.parents[1] / BUILDER_LOCK_PATH - if not builder_lock.is_file(): - raise RuntimeError(f"the tracked builder lock is missing: {builder_lock}") frames, actions = _timeline(spec) start_us = int(spec["start_timestamp_us"]) started_at = start_us / 1_000_000 @@ -460,8 +461,6 @@ def generate_fixture( "sqlite_version": REQUIRED_SQLITE_VERSION, "sqlalchemy_version": REQUIRED_SQLALCHEMY_VERSION, "database_normalization": "vacuum_4096_writer_version_zero", - "lock_path": BUILDER_LOCK_PATH, - "lock_sha256": sha256_file(builder_lock), }, "timing": { "kind": "declared_synthetic", diff --git a/tests/test_synthetic_fixtures.py b/tests/test_synthetic_fixtures.py index 76b49d4..dc8375a 100644 --- a/tests/test_synthetic_fixtures.py +++ b/tests/test_synthetic_fixtures.py @@ -4,6 +4,7 @@ import hashlib import json +import shutil import sqlite3 import struct import sys @@ -15,11 +16,11 @@ from openadapt_capture.capture import CaptureSession from openadapt_capture.terminal import CaptureSealError, verify_capture_artifacts from scripts.generate_synthetic_captures import ( - BUILDER_LOCK_PATH, GENERATED_FILENAMES, REQUIRED_SQLALCHEMY_VERSION, REQUIRED_SQLITE_VERSION, Canvas, + canonical_json_bytes, check_generated, ) from scripts.migrate_legacy_capture import migrate @@ -80,7 +81,7 @@ def test_public_fixture_is_synthetic_sealed_and_exactly_bound( provenance = json.loads(provenance_raw) assert provenance_raw.endswith(b"\n") assert provenance["schema_version"] == ( - "openadapt.capture.synthetic-fixture-provenance/v1" + "openadapt.capture.synthetic-fixture-provenance/v2" ) assert provenance["fixture_id"] == fixture_id assert provenance["synthetic"] is True @@ -94,8 +95,6 @@ def test_public_fixture_is_synthetic_sealed_and_exactly_bound( assert provenance["generator"]["sha256"] == _sha256(GENERATOR.read_bytes()) assert provenance["builder"] == { "database_normalization": "vacuum_4096_writer_version_zero", - "lock_path": BUILDER_LOCK_PATH, - "lock_sha256": _sha256((REPOSITORY / BUILDER_LOCK_PATH).read_bytes()), "sqlalchemy_version": REQUIRED_SQLALCHEMY_VERSION, "sqlite_version": REQUIRED_SQLITE_VERSION, } @@ -146,10 +145,38 @@ def test_public_fixture_is_synthetic_sealed_and_exactly_bound( assert all(token not in lowered for token in PRIVATE_SOURCE_TOKENS) +def _is_the_reference_builder() -> bool: + """Report whether this interpreter regenerates the committed bytes exactly. + + Python 3.12 with the required SQLite and SQLAlchemy is the one environment + that `test_public_fixtures_match_the_source_generator_byte_for_byte` proves + reproduces every committed byte. A tamper test is only meaningful there: on + any other builder a raised `generated bytes are stale` could report the + builder rather than the tamper. + """ + import sqlalchemy + + return sys.version_info[:2] == (3, 12) and ( + sqlite3.sqlite_version, + sqlalchemy.__version__, + ) == (REQUIRED_SQLITE_VERSION, REQUIRED_SQLALCHEMY_VERSION) + + +REFERENCE_BUILDER_ONLY = pytest.mark.skipif( + not _is_the_reference_builder(), + reason="only the reference builder regenerates the committed bytes exactly", +) + + +def _copy_committed_fixtures(destination: Path) -> Path: + for fixture_id in EXPECTED_COUNTS: + shutil.copytree(CAPTURE_ROOT / fixture_id, destination / fixture_id) + return destination + + def test_public_fixtures_match_the_source_generator_byte_for_byte() -> None: import sqlalchemy - assert (REPOSITORY / BUILDER_LOCK_PATH).is_file() if sys.version_info[:2] != (3, 12): actual = (sqlite3.sqlite_version, sqlalchemy.__version__) expected = (REQUIRED_SQLITE_VERSION, REQUIRED_SQLALCHEMY_VERSION) @@ -168,6 +195,51 @@ def test_public_fixtures_match_the_source_generator_byte_for_byte() -> None: ) +@REFERENCE_BUILDER_ONLY +def test_a_changed_source_spec_makes_the_committed_fixtures_stale( + tmp_path: Path, +) -> None: + """A source change reaches the fixture bytes, and the byte check reports it.""" + spec_root = tmp_path / "specs" + spec_root.mkdir() + for spec_path in (CAPTURE_ROOT / "specs").glob("*.json"): + spec = json.loads(spec_path.read_bytes()) + if spec_path.stem == "demo_new": + spec["actions"][0]["x"] += 1 + (spec_root / spec_path.name).write_bytes(canonical_json_bytes(spec)) + + with pytest.raises(RuntimeError, match="generated bytes are stale"): + check_generated( + spec_root=spec_root, + output_root=CAPTURE_ROOT, + generator_path=GENERATOR, + ) + + +@REFERENCE_BUILDER_ONLY +@pytest.mark.parametrize( + "artifact_name", + sorted(GENERATED_FILENAMES), +) +def test_every_committed_fixture_artifact_is_byte_checked( + tmp_path: Path, + artifact_name: str, +) -> None: + """One flipped byte in any generated artifact fails the byte check.""" + output_root = _copy_committed_fixtures(tmp_path / "captures") + target = output_root / "demo_new" / artifact_name + raw = bytearray(target.read_bytes()) + raw[-1] ^= 0xFF + target.write_bytes(bytes(raw)) + + with pytest.raises(RuntimeError, match="generated bytes are stale"): + check_generated( + spec_root=CAPTURE_ROOT / "specs", + output_root=output_root, + generator_path=GENERATOR, + ) + + def test_reconstructed_legacy_fixture_is_marked_unsealed_and_ineligible( tmp_path: Path, ) -> None: