diff --git a/docs/javascript-application-workflows.md b/docs/javascript-application-workflows.md index 6134a4c3f..e4a419bc9 100644 --- a/docs/javascript-application-workflows.md +++ b/docs/javascript-application-workflows.md @@ -95,6 +95,14 @@ matches exactly. Otherwise the result recommends provider-neutral follow-up tools and reports `requires-provider-analysis`; it never starts or switches a provider implicitly. +A handoff requires the add-on's exact content identity. Unavailable unpacked +companions remain visible in the trace graph with their canonical path, +availability and container-backed Evidence. They yield no actionable handoff +until their bytes are available and the application is reanalyzed; the archive +digest does not identify the missing add-on. An empty handoff list does not prove +that the trace contains no native components. Source partial coverage remains +visible independently of handoff eligibility. + ## Semantic relation tracing `trace_javascript_semantics` queries the authenticated companion graph without diff --git a/docs/verification/javascript-native-handoff-identity-2026-10-15.json b/docs/verification/javascript-native-handoff-identity-2026-10-15.json new file mode 100644 index 000000000..9a58a7ddc --- /dev/null +++ b/docs/verification/javascript-native-handoff-identity-2026-10-15.json @@ -0,0 +1,1052 @@ +{ + "base_revision": "9f8659e0536e4e50a438f5b9d5661f3c9624f6ac", + "scope": "Canonical native byte identity for actionable feature-trace handoffs; static shipped Joplin, not native provider/application behavior", + "runtime": { + "platform": "Linux x64", + "node": "24.18.0", + "npm": "11.16.0", + "tiktoken": "0.12.0", + "tokenizer": "cl100k_base" + }, + "acquisition": { + "repository": "laurent22/joplin", + "tag": "v3.7.21", + "published_at": "2026-09-25T21:31:12Z", + "url": "https://github.com/laurent22/joplin/releases/download/v3.7.21/Joplin-3.7.21.AppImage", + "asset_bytes": 201462481, + "asset_sha256": "5d202dcaca31e14206826aa3f04125e7c218dbc3dc3d98479d811efbd6b856c1", + "extraction": "7z 26.02 read-only selected extraction; no application/provider executed; AppImage SquashFS at offset 944632; one trailing-data warning, exit 0", + "files": [ + { + "path": "app.asar", + "bytes": 233575014, + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + { + "path": "app.asar.unpacked/node_modules/keytar/build/Release/keytar.node", + "bytes": 76960, + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + { + "path": "app.asar.unpacked/node_modules/onnxruntime-node/bin/napi-v6/linux/x64/onnxruntime_binding.node", + "bytes": 384040, + "sha256": "a7191fbfe8045d4d5d312e5ecd8f5dd6970e804dd340c98be8a0af8daf7908cc" + }, + { + "path": "app.asar.unpacked/node_modules/sqlite3/lib/binding/napi-v6-linux-glibc-x64/node_sqlite3.node", + "bytes": 2158008, + "sha256": "1f8cc72dfb50f0c54b04dd7a709ef5ef89b2b1f3b063270646bd12c81173b2e8" + } + ] + }, + "inventory_header_observation": { + "files": 6324, + "unpacked_entries": 46, + "unpacked_native_addons": 3, + "selected_static_sources": ["package.json"], + "native_companions_staged_for_available_control": 3, + "other_unpacked_entries_not_staged": 43, + "application_execution": false, + "native_provider_execution": false + }, + "baseline_source_sha256": { + "package.json": "56ecf79edad7f36f425288d80dee14247c7f24e3c7eeec40f95d9b8a62b0880d", + "src/domain/javascript/javascriptNativeHandoff.ts": "ce46a23c936940f1b57cf3c79082438ac7ba95652d671ff1befecbc24a6a2d7a", + "src/domain/javascript/javascriptFeatureTrace.ts": "b2122e4ae026e176dca775351101ad2ce876e3bf804f39554a2d9344d8fe1fd1", + "src/application/javascript/JavaScriptArtifactGraphStructure.ts": "1afdeda88a998133eb2de2cb26cbcffdd51c85f302c2797158e2944874326449", + "src/domain/javascript/javascriptApplicationEvidenceSchemas.ts": "b5229e409aa0b2a9e1470254ca93ab987ca26f1bc9e9f94ab44e89c749df59ca", + "src/domain/evidence.ts": "81e434981572df8c5af1dd5add04bcafc1fbfbf7b9a8dcc5a4b18bbd121cb3c4" + }, + "baseline_verifier_and_regression_source": { + "base_revision": "9f8659e0536e4e50a438f5b9d5661f3c9624f6ac", + "source_sha256": { + "scripts/verify/javascript/native-handoffs.mjs": "9950693500d94aa28b01b2e7e8b8e3e799064f3fc53eef10a65d129f4c6c1f54", + "tests/boundary/mcp/javascriptNativeHandoffIdentity.test.ts": "ff247a8fb2189bf40cab604af23fc792eee939ff93f6c0f8e153a28405b3bdd9", + "src/domain/javascript/javascriptNativeHandoff.ts": "ce46a23c936940f1b57cf3c79082438ac7ba95652d671ff1befecbc24a6a2d7a", + "src/domain/javascript/javascriptFeatureTrace.ts": "b2122e4ae026e176dca775351101ad2ce876e3bf804f39554a2d9344d8fe1fd1", + "src/application/javascript/JavaScriptArtifactGraphStructure.ts": "1afdeda88a998133eb2de2cb26cbcffdd51c85f302c2797158e2944874326449" + }, + "verifier_optional_architecture_correction": { + "current_sha256": "bfdc235cc38a519a39e8b2e772d55259f371ccb15058e6fd241ee5ac05dfef82", + "explanation": "Omitted the internal factory optional architecture field instead of passing null. All four real-case normalized simulated Evidence records proved byte-identical; runtime tool requests/results unchanged.", + "normalized_input_equivalence": [ + { + "mode": "available", + "kind": "container", + "evidence_id": "ev_a67558e076894c3b7d339257765f22d66987cbcef411bf92d1dc50db2f4521ca", + "normalized_bytes_identical": true + }, + { + "mode": "available", + "kind": "native", + "evidence_id": "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7", + "normalized_bytes_identical": true + }, + { + "mode": "missing", + "kind": "container", + "evidence_id": "ev_a67558e076894c3b7d339257765f22d66987cbcef411bf92d1dc50db2f4521ca", + "normalized_bytes_identical": true + }, + { + "mode": "missing", + "kind": "native", + "evidence_id": "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7", + "normalized_bytes_identical": true + } + ] + } + }, + "final_source_sha256": { + "package.json": "cdb2fb0fd3a88790d921aadb560cac382fa360c821a2c3f6960bf75eff292ee8", + "src/domain/javascript/javascriptNativeHandoff.ts": "6a0c6277dcbd7a5b79bf926d2191afe69af79993bb30c8823c833ccfd946c68c", + "src/domain/javascript/javascriptFeatureTrace.ts": "b2122e4ae026e176dca775351101ad2ce876e3bf804f39554a2d9344d8fe1fd1", + "src/application/javascript/JavaScriptArtifactGraphStructure.ts": "1afdeda88a998133eb2de2cb26cbcffdd51c85f302c2797158e2944874326449", + "src/domain/javascript/javascriptApplicationEvidenceSchemas.ts": "b5229e409aa0b2a9e1470254ca93ab987ca26f1bc9e9f94ab44e89c749df59ca", + "src/domain/evidence.ts": "81e434981572df8c5af1dd5add04bcafc1fbfbf7b9a8dcc5a4b18bbd121cb3c4", + "scripts/verify/javascript/native-handoffs.mjs": "bfdc235cc38a519a39e8b2e772d55259f371ccb15058e6fd241ee5ac05dfef82", + "tests/boundary/mcp/javascriptNativeHandoffIdentity.test.ts": "fe216875d7bc080ddcc72a3ae3da38d0120f918ad1d6d5caa2b41edbc4796ddb" + }, + "cohorts": { + "baseline": { + "available": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/available/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "trace_evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051", + "preparation_ms": 38140.725152, + "samples": [ + { + "round": 0, + "elapsed_ms": 150.19293800000014, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 1, + "elapsed_ms": 108.26803500000096, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 2, + "elapsed_ms": 93.04797500000132, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + } + ], + "safe_identity": true, + "node": { + "node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "identity": { + "strategy": "content-digest", + "stability": "global-exact", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "unavailable_observed": false, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [ + { + "native_node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "artifact_sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "artifact_path": "node_modules/keytar/build/Release/keytar.node", + "export_node_ids": [], + "requested_exports": [], + "status": "evidence-linked", + "providers": [ + { + "id": "ghidra", + "name": "Ghidra", + "version": "simulated" + } + ], + "evidence_ids": [ + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "recommended_tools": [ + "open_binary", + "binary_overview", + "search_procedures", + "analyze_function", + "xrefs" + ] + } + ], + "evidence_links": [ + "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 3 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "539049c79671407c82040fd9ce443141c78fca5403daadbaf5ca82678fd0c933", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 601, + "structured_replies": 6169, + "full_tool_replies": 12735, + "selected_catalog_structured_task": 12340, + "full_catalog_structured_task": 202292, + "selected_catalog_full_reply_task": 18906, + "full_catalog_full_reply_task": 208858 + } + }, + "resources": { + "elapsed_seconds": 60.369, + "cpu_seconds": 59.80667, + "peak_cgroup_bytes": 2662629376, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + }, + "missing": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/missing/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": null, + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536", + "trace_evidence_id": "ev_7bc771bc1fc4b3eb1a666107bd9a49daddcfd7c90f048414f11862a3ca5f3f84", + "preparation_ms": 36053.453958, + "samples": [ + { + "round": 0, + "elapsed_ms": 145.345276, + "structured_reply_bytes": 10008, + "full_tool_reply_bytes": 20875 + }, + { + "round": 1, + "elapsed_ms": 99.91117400000076, + "structured_reply_bytes": 10008, + "full_tool_reply_bytes": 20875 + }, + { + "round": 2, + "elapsed_ms": 89.61581300000398, + "structured_reply_bytes": 10008, + "full_tool_reply_bytes": 20875 + } + ], + "safe_identity": false, + "node": { + "node_id": "jag_node_dedeb2467dfe8cf75eee6588ce33416450e2c02cd67fb8ddff784449a2ec67f0", + "identity": { + "strategy": "canonical-path", + "stability": "artifact-version", + "artifact_sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "path": "node_modules/keytar/build/Release/keytar.node" + }, + "unavailable_observed": true, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [ + { + "native_node_id": "jag_node_dedeb2467dfe8cf75eee6588ce33416450e2c02cd67fb8ddff784449a2ec67f0", + "artifact_sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "artifact_path": "node_modules/keytar/build/Release/keytar.node", + "export_node_ids": [], + "requested_exports": [], + "status": "evidence-linked", + "providers": [ + { + "id": "ghidra", + "name": "Ghidra", + "version": "simulated" + } + ], + "evidence_ids": [ + "ev_a67558e076894c3b7d339257765f22d66987cbcef411bf92d1dc50db2f4521ca" + ], + "recommended_tools": [ + "open_binary", + "binary_overview", + "search_procedures", + "analyze_function", + "xrefs" + ] + } + ], + "evidence_links": [ + "ev_a67558e076894c3b7d339257765f22d66987cbcef411bf92d1dc50db2f4521ca", + "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 2 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "992f22f9f4979b07bd17ca5ef6c000d31ab6bab8dcbd52d330214bc92b8456b4", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_7bc771bc1fc4b3eb1a666107bd9a49daddcfd7c90f048414f11862a3ca5f3f84" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 594, + "structured_replies": 5208, + "full_tool_replies": 10789, + "selected_catalog_structured_task": 11372, + "full_catalog_structured_task": 201324, + "selected_catalog_full_reply_task": 16953, + "full_catalog_full_reply_task": 206905 + } + }, + "resources": { + "elapsed_seconds": 57.93, + "cpu_seconds": 57.479109, + "peak_cgroup_bytes": 2719457280, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + } + }, + "final": { + "available": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/available/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "trace_evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051", + "preparation_ms": 116767.19654199999, + "samples": [ + { + "round": 0, + "elapsed_ms": 1120.7472280000075, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 1, + "elapsed_ms": 1173.7802859999938, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 2, + "elapsed_ms": 1782.2942279999988, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + } + ], + "safe_identity": true, + "node": { + "node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "identity": { + "strategy": "content-digest", + "stability": "global-exact", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "unavailable_observed": false, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [ + { + "native_node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "artifact_sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "artifact_path": "node_modules/keytar/build/Release/keytar.node", + "export_node_ids": [], + "requested_exports": [], + "status": "evidence-linked", + "providers": [ + { + "id": "ghidra", + "name": "Ghidra", + "version": "simulated" + } + ], + "evidence_ids": [ + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "recommended_tools": [ + "open_binary", + "binary_overview", + "search_procedures", + "analyze_function", + "xrefs" + ] + } + ], + "evidence_links": [ + "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 3 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "539049c79671407c82040fd9ce443141c78fca5403daadbaf5ca82678fd0c933", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 601, + "structured_replies": 6169, + "full_tool_replies": 12735, + "selected_catalog_structured_task": 12340, + "full_catalog_structured_task": 202292, + "selected_catalog_full_reply_task": 18906, + "full_catalog_full_reply_task": 208858 + } + }, + "resources": { + "elapsed_seconds": 258.885, + "cpu_seconds": 106.221003, + "peak_cgroup_bytes": 2678456320, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + }, + "missing": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/missing/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": null, + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536", + "trace_evidence_id": "ev_3931e0874b0cffffa10cfe0124d218f09e6b7edbdda789591a7593060afd6450", + "preparation_ms": 36650.457516, + "samples": [ + { + "round": 0, + "elapsed_ms": 156.684454000002, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + }, + { + "round": 1, + "elapsed_ms": 100.968246000004, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + }, + { + "round": 2, + "elapsed_ms": 90.89984700000059, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + } + ], + "safe_identity": true, + "node": { + "node_id": "jag_node_dedeb2467dfe8cf75eee6588ce33416450e2c02cd67fb8ddff784449a2ec67f0", + "identity": { + "strategy": "canonical-path", + "stability": "artifact-version", + "artifact_sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "path": "node_modules/keytar/build/Release/keytar.node" + }, + "unavailable_observed": true, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [], + "evidence_links": [ + "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 2 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "aae3c060eb63eb75816c4fb6e552eac6055c8195ae4d1e5e9d4c5125618ce2a0", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_3931e0874b0cffffa10cfe0124d218f09e6b7edbdda789591a7593060afd6450" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 594, + "structured_replies": 4939, + "full_tool_replies": 10237, + "selected_catalog_structured_task": 11103, + "full_catalog_structured_task": 201055, + "selected_catalog_full_reply_task": 16401, + "full_catalog_full_reply_task": 206353 + } + }, + "resources": { + "elapsed_seconds": 59.738, + "cpu_seconds": 59.254776, + "peak_cgroup_bytes": 2571235328, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + } + } + }, + "resource_policy": { + "hard_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "cpu_affinity": [15], + "memory_accounting": "Cgroup family peak includes charged file cache; not summed RSS. Counters at payload exit while observer remains alive, not final whole-scope counters.", + "cache_conditions": "Input is stream-hashed before analysis, warming its file cache. Builds and OS caches uncontrolled; fresh MCP process is not an OS-cold run.", + "performance_scope": "Whole verifier includes input hashing, preparation, repeated queries, export, CLI parity and tokenization. Per-query samples are wall times only; isolated query CPU/memory and hosted-model costs unmeasured. Final available control258.885s wall/106.221003s CPU is substantially slower than baseline60.369s/59.806670s; final missing control59.738s/59.254776s and prior postfix controls are separately preserved. Timing/resource variability is unresolved, not attributed to this native consumer change. No speedup or performance-equivalence claim." + }, + "observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + }, + "validation": { + "harness_attempt": "Initial CLI trace input incorrectly used the MCP inline selector wrapper; both cases rejected before identity assertions. Corrected to the CLI complete-Evidence contract. Not a product reproduction.", + "baseline_public": "Missing-companion failed / available-byte control passed. Both adapters and both native-observation selections executed before identity assertions.", + "initial_focused": "24 passed in four files including public CLI/stdio MCP, workflow/graph, missing companion imports and erased TypeScript native bindings.", + "first_static": "Other static gates passed, typecheck rejected architecture:null in test factory. Omitted optional field; all four real-case normalized simulated Evidence inputs proved byte-identical. No product behavior change.", + "final_public": "2/2 public CLI/stdio MCP cases passed after named summary parsing and optional architecture correction; overlaps initial 24-case selection.", + "final_static_docs": "npm run check && npm run docs:check passed on integration base62bf098 plus this final change; typecheck, lint, format, knip, metadata, architecture/module boundaries and docs checks passed, including5 consistency tests. Prior static attempt/failure retained separately.", + "synced_cli_workflows": "34 passed in tests/acceptance/applications/applicationWorkflowCli.test.ts after sync to62bf098; adds a fifth file and34 distinct cases to initial24 (58 total distinct focused cases)." + }, + "interpretation": "Unavailable case previously returned a successful transport response with incorrect identity/linkage; this is not a successful equivalent analyst task and earns no efficiency credit. Available case is the valid completed-equivalence control. Reduced missing-case bytes/tokens reflect removal of unsupported claims, not an independent efficiency gain.", + "verifier_conditions": "Fresh MCP session, one selected analysis followed by repeated identical retained traces. Filesystem/build caches uncontrolled. Task token counts include one analysis summary and one trace with name/description/inputSchema catalog projections; structured and complete-tool-reply projections are alternatives, not billing. Repeated rounds, export, CLI parity and tokenizer are validation overhead outside that one-task token count but inside total resource receipts. A wrong-identity baseline is not a successful equivalent task or efficiency credit.", + "verifier_scope": "Real shipped-ASAR native identity through public CLI/stdio MCP; simulated native Evidence only, no provider/application executed", + "integration_base_revision": "62bf09809ef7bb224028916424f8f07652a20f4f", + "upstream_sync": "Fast-forwarded to reviewed main62bf098. Six upstream commits change CLI acceptance plus Apple, browser type-import parsing, reference JSON resolution and Kuna caching; no owned/key native source files overlap. Native source hashes unchanged. Updated public workflow acceptance34/34 passed; both real controls repeated on this integration base. Final static/docs gates recorded separately.", + "prior_postfix_controls": { + "available": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/available/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "trace_evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051", + "preparation_ms": 37729.472507, + "samples": [ + { + "round": 0, + "elapsed_ms": 157.96848200000386, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 1, + "elapsed_ms": 114.56066299999657, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + }, + { + "round": 2, + "elapsed_ms": 99.90600200000335, + "structured_reply_bytes": 12372, + "full_tool_reply_bytes": 25845 + } + ], + "safe_identity": true, + "node": { + "node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "identity": { + "strategy": "content-digest", + "stability": "global-exact", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "unavailable_observed": false, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [ + { + "native_node_id": "jag_node_98ccdef0e85d67cba9cc843193c5ba877f0a73ba3fa7d3581f6db0396a707b2d", + "artifact_sha256": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "artifact_path": "node_modules/keytar/build/Release/keytar.node", + "export_node_ids": [], + "requested_exports": [], + "status": "evidence-linked", + "providers": [ + { + "id": "ghidra", + "name": "Ghidra", + "version": "simulated" + } + ], + "evidence_ids": [ + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "recommended_tools": [ + "open_binary", + "binary_overview", + "search_procedures", + "analyze_function", + "xrefs" + ] + } + ], + "evidence_links": [ + "ev_7d6754f5db07c44889b44ba2a14d235db79b94e9967c8ad36fe6b759615d0b7b", + "ev_c1bf000c14a18445db1f834b1c6019ae04725dd948951d5b5079184b4524e8d7" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 3 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "539049c79671407c82040fd9ce443141c78fca5403daadbaf5ca82678fd0c933", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_2b1e20e64bf4aef770a556032dd72d70339baba21dc4f4ef8b34601b5ab55051" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 601, + "structured_replies": 6169, + "full_tool_replies": 12735, + "selected_catalog_structured_task": 12340, + "full_catalog_structured_task": 202292, + "selected_catalog_full_reply_task": 18906, + "full_catalog_full_reply_task": 208858 + } + }, + "resources": { + "elapsed_seconds": 61.657, + "cpu_seconds": 61.135962, + "peak_cgroup_bytes": 3146776576, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + }, + "missing": { + "result": { + "artifact": { + "path": "/tmp/rea-native-handoff-20261015/cases/missing/app.asar", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "addon_path": "node_modules/keytar/build/Release/keytar.node", + "companion_sha256_observed": null, + "prior_native_sha256_supplied": "0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a", + "source_paths": ["package.json"], + "parent_evidence_id": "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536", + "trace_evidence_id": "ev_3931e0874b0cffffa10cfe0124d218f09e6b7edbdda789591a7593060afd6450", + "preparation_ms": 51944.249193999996, + "samples": [ + { + "round": 0, + "elapsed_ms": 140.67567200000485, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + }, + { + "round": 1, + "elapsed_ms": 105.40526200000022, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + }, + { + "round": 2, + "elapsed_ms": 88.52443199999834, + "structured_reply_bytes": 9298, + "full_tool_reply_bytes": 19401 + } + ], + "safe_identity": true, + "node": { + "node_id": "jag_node_dedeb2467dfe8cf75eee6588ce33416450e2c02cd67fb8ddff784449a2ec67f0", + "identity": { + "strategy": "canonical-path", + "stability": "artifact-version", + "artifact_sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "path": "node_modules/keytar/build/Release/keytar.node" + }, + "unavailable_observed": true, + "basis": [ + { + "authority": "artifact-bytes", + "state": "observed", + "confidence": "exact", + "artifact": { + "available": true, + "artifact_id": "art_767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a", + "sha256": "767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a" + }, + "location": { + "available": true, + "value": { + "kind": "artifact-path", + "path": "node_modules/keytar/build/Release/keytar.node" + } + } + } + ] + }, + "native_handoffs": [], + "evidence_links": [ + "ev_ac00ab67200361a181e08d358b591983616044f39c972ee81e65e49a0d8a0536" + ], + "source_coverage": { + "status": "partial", + "source_graph_status": "partial", + "total_seed_matches": 2 + }, + "graph_coverage": { + "status": "partial", + "truncated": false, + "omitted_count": null, + "limits": [] + }, + "facts_sha256": "aae3c060eb63eb75816c4fb6e552eac6055c8195ae4d1e5e9d4c5125618ce2a0", + "cli_parity": { + "exact_cli_mcp_trace_parity": true, + "evidence_id": "ev_3931e0874b0cffffa10cfe0124d218f09e6b7edbdda789591a7593060afd6450" + }, + "token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "selected_catalog": 5570, + "full_catalog": 195522, + "requests": 594, + "structured_replies": 4939, + "full_tool_replies": 10237, + "selected_catalog_structured_task": 11103, + "full_catalog_structured_task": 201055, + "selected_catalog_full_reply_task": 16401, + "full_catalog_full_reply_task": 206353 + } + }, + "resources": { + "elapsed_seconds": 74.956, + "cpu_seconds": 60.352428, + "peak_cgroup_bytes": 3124801536, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + } + }, + "prior_postfix_controls_note": "Executed before upstream sync, on base9f8659e plus this native fix. Retained, not asserted as a final-integration measurement.", + "validation_resources": { + "synced_cli_34": { + "elapsed_seconds": 240.873, + "cpu_seconds": 141.092204, + "peak_cgroup_bytes": 3220983808, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "initial_focused_24": { + "elapsed_seconds": 86.733, + "cpu_seconds": 85.48165, + "peak_cgroup_bytes": 3216478208, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "final_public_2": { + "elapsed_seconds": 78.183, + "cpu_seconds": 32.274966, + "peak_cgroup_bytes": 1365196800, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "prior_static_docs": { + "elapsed_seconds": 169.015, + "cpu_seconds": 119.713, + "peak_cgroup_bytes": 4265230336, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + }, + "final_integration_static_docs": { + "elapsed_seconds": 128.732, + "cpu_seconds": 115.51892, + "peak_cgroup_bytes": 4301684736, + "payload_cleanup_verified": true, + "events_at_payload_exit": { + "memory.events": { + "low": 0, + "high": 0, + "max": 0, + "oom": 0, + "oom_kill": 0, + "oom_group_kill": 0 + }, + "pids.events": { + "max": 0 + } + } + } + }, + "integration_equivalence": "Final controls on62bf098 preserve all recorded available-case artifact, identity, facts, Evidence, coverage and token fields from baseline; missing-case parent/node/provenance/coverage unchanged, handoff/container link removed. Both exactly equal the earlier postfix controls in all recorded non-timing contract fields.", + "boundary_audit": "Unavailable native producer owns canonical-path identity and archive-backed observations; native handoff derivation now consumes only its content-digest identity. Feature trace is the only production caller. Application-version matching/content comparison already consume canonical identity rather than a supporting observation digest; no duplicate fallback found there. CLI/MCP use the same application workflow." +} diff --git a/docs/verification/javascript-native-handoff-identity-2026-10-15.md b/docs/verification/javascript-native-handoff-identity-2026-10-15.md new file mode 100644 index 000000000..c3bd79e91 --- /dev/null +++ b/docs/verification/javascript-native-handoff-identity-2026-10-15.md @@ -0,0 +1,179 @@ +# Native handoff byte-identity verification + +Tracks #3926 under #4027. Baseline: `9f8659e0536e4e50a438f5b9d5661f3c9624f6ac`. +Final integration base: `62bf09809ef7bb224028916424f8f07652a20f4f`. + +The analyst already knows an ASAR-relative native path and supplies prior native +Evidence. The question is which Evidence can be linked to that component's exact +bytes. Discovery and native-provider execution are outside this task. + +## Observed behavior and correction + +The native-node producer distinguishes a content digest from a canonical path +whose bytes are unavailable. Its unavailable observation legitimately cites the +containing archive. The handoff consumer previously treated that supporting +artifact digest as the native component's digest. + +Public CLI/stdio MCP regression: the missing-companion case failed and the +available-byte control passed. Real Joplin reproduced the same result: missing +keytar bytes produced an `evidence-linked` handoff with the ASAR digest and the +simulated container observation. CLI/MCP agreed on that incorrect answer. + +The consumer now uses only the node owner's content-digest identity. The fallback +observation scan is removed. Unavailable native nodes retain their canonical +path, availability, container-backed provenance and partial coverage in the +inline graph; they yield no false actionable handoff or native Evidence link. +An empty handoff list describes eligibility, not absence of native components. +The per-query node index is allocated only after an eligible target exists; no +persistent cache, schema, tool or target authority is added. + +Final real checks passed both cases with exact CLI/MCP trace parity. The available +control preserved the complete parent/trace Evidence IDs, native facts, links, +coverage and all token projections. The missing control preserved its parent, +native node, source provenance and coverage, while removing the wrong handoff +and container Evidence link. + +## Real input and scope + +[Official Joplin v3.7.21 Linux AppImage](https://github.com/laurent22/joplin/releases/download/v3.7.21/Joplin-3.7.21.AppImage), +published 2026-09-25, 201,462,481 bytes, SHA-256 +`5d202dcaca31e14206826aa3f04125e7c218dbc3dc3d98479d811efbd6b856c1`. + +Read-only 7z 26.02 inspection found 6,324 ASAR files, 46 unpacked entries and +three `.node` components. Extract only the ASAR and those three binaries: + +```sh +input_root="$(mktemp -d -t rea-native-handoff-XXXXXX)" +curl --fail --location --output "$input_root/Joplin-3.7.21.AppImage" \ + https://github.com/laurent22/joplin/releases/download/v3.7.21/Joplin-3.7.21.AppImage +sha256sum "$input_root/Joplin-3.7.21.AppImage" +7z x -mmt=1 "-o$input_root/extracted" "$input_root/Joplin-3.7.21.AppImage" \ + resources/app.asar \ + resources/app.asar.unpacked/node_modules/keytar/build/Release/keytar.node \ + resources/app.asar.unpacked/node_modules/sqlite3/lib/binding/napi-v6-linux-glibc-x64/node_sqlite3.node \ + resources/app.asar.unpacked/node_modules/onnxruntime-node/bin/napi-v6/linux/x64/onnxruntime_binding.node +python3 - "$input_root" <<'PY' +import os, sys +from pathlib import Path +root = Path(sys.argv[1]) +source = root / 'extracted/resources' +for mode in ('available', 'missing'): + case = root / 'cases' / mode + case.mkdir(parents=True) + os.link(source / 'app.asar', case / 'app.asar') + if mode == 'available': + for native in (source / 'app.asar.unpacked').rglob('*.node'): + target = case / native.relative_to(source) + target.parent.mkdir(parents=True, exist_ok=True) + os.link(native, target) +PY +``` + +Verify the asset digest above before extraction. The ASAR is 233,575,014 bytes, +SHA-256 `767df409b18effe60421fbefcca873de237c7e813eac744ecdeebbc5e825a33a`. +Keytar is 76,960 bytes, SHA-256 +`0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a`. +All extracted component hashes are in the JSON record. 7z reported one +trailing-data warning for the AppImage's embedded SquashFS and exited 0. + +Both cases select only `package.json` for static source analysis while retaining +the inventory. The available case stages all three native companions; the other +43 unpacked entries are not staged. The missing case stages none. Whole-application +coverage is therefore partial in both cases, independently of the selected +native component's known or unknown byte identity. Neither the application nor +a native analysis provider is executed. Native Evidence is explicitly simulated +to test exact subject linkage. Live Electron, real Ghidra/Hopper/IDA behavior and +Windows/macOS verification are **NOT_ENOUGH_INFO**. + +## Reproduce the public workflow + +Use pinned Node 24.18.0/npm 11.16.0, `npm ci`, then `npm run build:cached`. +The verifier returns compact records and deletes its private exported parent and +CLI input after owned cleanup. No downloadable application or large Evidence is +archived. + +```sh +for mode in available missing; do + node scripts/verify/javascript/native-handoffs.mjs \ + --artifact "$input_root/cases/$mode/app.asar" \ + --addon-path node_modules/keytar/build/Release/keytar.node \ + --native-sha256 0700016bcd761cbe02d275f2046c7cc42f39f730a50d66e3d3307aa9d413373a \ + --source-path package.json --rounds 3 --cli-parity --require-safe +done +``` + +Add `--tokenizer-python /absolute/path/to/python-with-tiktoken-0.12.0` for the +reported BPE projections. The npm alias `verify:javascript:native-handoffs` +builds first; recorded costs use the direct Node invocation above after building. +For a pre-fix observation, run the saved verifier on the baseline build without +`--require-safe`; `safe_identity: false` is an incorrect answer, not a passing +analyst task. Paths affect serialized requests and some Evidence identities. + +The exact resource runs additionally used the preserved +[8 GiB observer](https://github.com/morluto/rea/blob/b3bdabab694af1f9801eff7caf75001b92314a58/experiments/analysis-efficiency/resource-run.py) +and adjacent `resource_payload.py`; both hashes are pinned in the JSON. + +## Completed workflow costs and limits + +| Case | Revision | Wall s | CPU s | Peak cgroup bytes | Identity oracle | +| ---------------------- | -------- | ------: | ---------: | ----------------: | -------------------------- | +| Native bytes available | Before | 60.369 | 59.806670 | 2,662,629,376 | pass | +| Native bytes available | After | 258.885 | 106.221003 | 2,678,456,320 | pass, exact baseline facts | +| Native bytes missing | Before | 57.930 | 57.479109 | 2,719,457,280 | wrong identity/linkage | +| Native bytes missing | After | 59.738 | 59.254776 | 2,571,235,328 | pass, unknown retained | + +Whole-verifier resources include input hashing, analysis, three retained trace +queries, export, CLI parity and tokenization. One CPU, 8 GiB hard cap, 6 GiB early +stop, no swap; cleanup verified and no OOM. Memory includes charged file cache, +not summed RSS. Counters are at payload exit while the observer remains alive. +The available control's final wall and CPU times are substantially higher than +baseline. Earlier post-fix controls on the original base took 61.657/74.956 s wall +and 61.135962/60.352428 s CPU for available/missing cases; their complete receipts +remain separately recorded. The final missing case took 59.738 s wall. This +variability is unresolved and cannot be attributed to the native consumer change. +These runs establish neither speedup nor performance equivalence. Input hashing warms the ASAR +cache, build/OS caches and shared-host scheduling are uncontrolled. Fresh MCP +sessions do not establish OS-cold conditions. First post-analysis and repeated +warm-query samples are preserved separately; query CPU/memory are unmeasured. + +One defined task is one analysis summary plus one retained trace, with the path +and prior Evidence already supplied. Static `cl100k_base`/tiktoken 0.12.0 counts +include both requests, replies and name/description/inputSchema contracts: + +| Available-byte task, unchanged before/after | BPE tokens | +| ----------------------------------------------- | ---------: | +| Selected catalog + structured replies | 12,340 | +| Selected catalog + complete tool replies | 18,906 | +| Full catalog projection + structured replies | 202,292 | +| Full catalog projection + complete tool replies | 208,858 | + +These are alternative reply/catalog projections. The selected catalog alone is +5,570 BPE; the full projection is 195,522. Output schemas, annotations, host +rendering/replay, reasoning, final answers and model billing are unmeasured. +Repeated queries/export/CLI/tokenization are validation overhead outside this +one-task token count, but inside resource receipts. Missing-case structured-task +counts change 11,372 → 11,103 by removing unsupported claims; the incorrect +baseline earns no efficiency credit. + +## Regression and verification record + +The initial four-file focused selection passed 24 tests. The two public cases +were rerun after adding named summary-result parsing and correcting an optional +architecture factory input. Omitting that field instead of passing null produced +byte-identical normalized simulated Evidence in all four real-case inputs. +The initial CLI-selector harness error and first typecheck failure are retained +as harness/validation attempts, not product reproductions. + +After syncing the reviewed upstream changes, the updated application workflow +CLI acceptance file passed 34 tests, bringing the focused selection to 58 distinct +cases across five files. Both Joplin controls were then repeated on the final +integration base. Canonical byte identity is also used by the related version +matching/content comparison consumers; no equivalent observation-digest fallback +was found there. + +Final `npm run check && npm run docs:check` passed on the integration base, +including five documentation consistency tests: 128.732 s wall, 115.518920 s CPU, +4,301,684,736 B peak cgroup memory, clean owned exit and no OOM. + +Final public and static/docs outcomes are recorded in the +[compact measurements and source pins](javascript-native-handoff-identity-2026-10-15.json). diff --git a/package.json b/package.json index 904a3806f..cf1a4defa 100644 --- a/package.json +++ b/package.json @@ -214,6 +214,7 @@ "verify:javascript:digests": "npm run build:cached && node scripts/verify/javascript/digests.mjs", "verify:javascript:output": "npm run build:cached && node scripts/verify/javascript/output.mjs", "verify:javascript:module-references": "npm run build:cached && node scripts/verify/javascript/module-references.mjs", + "verify:javascript:native-handoffs": "npm run build:cached && node scripts/verify/javascript/native-handoffs.mjs", "verify:application-workflows": "npm run build:cached && node scripts/verify/javascript/application-workflows.mjs", "verify:managed": "npm run build:cached && node scripts/verify/managed/conformance.mjs", "verify:firmware": "npm run build:cached && node scripts/verify/firmware/analysis.mjs", diff --git a/scripts/verify/javascript/native-handoffs.mjs b/scripts/verify/javascript/native-handoffs.mjs new file mode 100644 index 000000000..4ee67aa7c --- /dev/null +++ b/scripts/verify/javascript/native-handoffs.mjs @@ -0,0 +1,348 @@ +#!/usr/bin/env node + +import assert from "node:assert/strict"; +import { createHash, randomUUID } from "node:crypto"; +import { createReadStream } from "node:fs"; +import { readFile, writeFile } from "node:fs/promises"; +import { isAbsolute, join } from "node:path"; +import { performance } from "node:perf_hooks"; +import { fileURLToPath } from "node:url"; +import { parseArgs } from "node:util"; +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { createEvidence } from "../../../dist/domain/evidence.js"; +import { PrivateRuntimeRoot } from "../../../dist/process/PrivateRuntimeRoot.js"; +import { + OwnedCommandFailure, + runOwnedCommand, +} from "../../../dist/process/OwnedCommand.js"; +import { closeVerifierResources } from "../../lib/verifier-cleanup.mjs"; + +const { values } = parseArgs({ + options: { + artifact: { type: "string" }, + "addon-path": { type: "string" }, + "native-sha256": { type: "string" }, + "source-path": { type: "string", multiple: true }, + rounds: { type: "string", default: "3" }, + "cli-parity": { type: "boolean", default: false }, + "tokenizer-python": { type: "string" }, + "require-safe": { type: "boolean", default: false }, + }, + strict: true, +}); +assert.ok( + values.artifact && isAbsolute(values.artifact), + "--artifact must be absolute", +); +assert.ok( + values["addon-path"] && + !isAbsolute(values["addon-path"]) && + values["addon-path"] + .split("/") + .every((part) => part !== "" && part !== "." && part !== ".."), + "--addon-path must be a normalized archive-relative path", +); +assert.match( + values["native-sha256"] ?? "", + /^[a-f0-9]{64}$/, + "--native-sha256 pins prior native bytes; it does not prove availability", +); +const rounds = Number(values.rounds); +assert.ok(Number.isSafeInteger(rounds) && rounds > 0); +const environment = { + PATH: process.env.PATH ?? "/usr/bin:/bin", + REA_LOG_LEVEL: "silent", + REA_ANALYSIS_PROVIDER: "auto", + HOPPER_LAUNCHER_PATH: "/rea-unconfigured-deep-provider/hopper", +}; +const transport = new StdioClientTransport({ + command: process.execPath, + args: [fileURLToPath(new URL("../../rea.mjs", import.meta.url)), "mcp"], + env: environment, + stderr: "pipe", +}); +const client = new Client({ + name: "javascript-native-handoff-verifier", + version: "1", +}); +const serialize = (value) => JSON.stringify(value); +const digest = (value) => + createHash("sha256").update(serialize(value)).digest("hex"); +const fileDigest = async (path) => { + const hash = createHash("sha256"); + for await (const chunk of createReadStream(path)) hash.update(chunk); + return hash.digest("hex"); +}; +const simulatedNativeEvidence = (path, sha256) => + createEvidence( + { path, sha256, format: "raw-binary" }, + { id: "ghidra", name: "Ghidra", version: "simulated" }, + { + operation: "binary_overview", + parameters: {}, + result: { + summary: + "Simulated subject-linkage input; no native provider executed.", + }, + limitations: ["No native provider behavior is established."], + }, + ); +let temporary; +let primaryError; +let retainTemporary = false; +let report; +const ownedCommand = async (command, arguments_, timeoutMs = 60_000) => { + try { + return await runOwnedCommand( + { + command, + arguments: arguments_, + cwd: temporary.path, + env: environment, + runId: `rea-native-handoff-${randomUUID()}`, + expectedCommand: command === process.execPath ? process.execPath : null, + sweepTokenOwnedProcesses: true, + }, + { timeoutMs, diagnosticBytes: 16 * 1024 * 1024 }, + ); + } catch (cause) { + retainTemporary ||= + cause instanceof OwnedCommandFailure && cause.cleanupOwner !== undefined; + throw cause; + } +}; +try { + const archiveSha256 = await fileDigest(values.artifact); + const companion = join(`${values.artifact}.unpacked`, values["addon-path"]); + let companionSha256 = null; + try { + companionSha256 = await fileDigest(companion); + } catch (cause) { + if (cause?.code !== "ENOENT") throw cause; + } + if (companionSha256 !== null) + assert.equal(companionSha256, values["native-sha256"]); + const native = simulatedNativeEvidence(companion, values["native-sha256"]); + const container = simulatedNativeEvidence(values.artifact, archiveSha256); + await client.connect(transport); + transport.stderr?.on("data", () => undefined); + const tools = (await client.listTools()).tools; + const catalog = tools.map(({ name, description, inputSchema }) => ({ + name, + description, + inputSchema, + })); + const selected = catalog.filter(({ name }) => + ["analyze_javascript_application", "trace_application_feature"].includes( + name, + ), + ); + assert.equal(selected.length, 2); + const call = async (name, arguments_) => { + const response = await client.callTool( + { name, arguments: arguments_ }, + { timeout: 360_000 }, + ); + assert.notEqual(response.isError, true, serialize(response)); + assert.ok(response.structuredContent?.evidence_id); + return { + request: { name, arguments: arguments_ }, + response: response.structuredContent, + wire_response: response, + }; + }; + const prepStart = performance.now(); + const preparation = await call("analyze_javascript_application", { + input_path: values.artifact, + detail: "summary", + ...(values["source-path"] === undefined + ? {} + : { source_paths: values["source-path"] }), + }); + const preparationMs = performance.now() - prepStart; + assert.equal(preparation.response.subject.digest.sha256, archiveSha256); + const sourceId = preparation.response.normalized_result.parent_evidence_id; + assert.match(sourceId, /^ev_[a-f0-9]{64}$/); + const query = { + application: { kind: "retained-evidence", evidence_id: sourceId }, + native_observations: [container, native], + seed: { + kind: "string", + value: values["addon-path"], + match: "exact", + case_sensitive: true, + }, + direction: "outgoing", + }; + const samples = []; + let first; + for (let round = 0; round < rounds; round++) { + const start = performance.now(); + const result = await call("trace_application_feature", query); + const elapsedMs = performance.now() - start; + if (first === undefined) first = result; + else assert.equal(result.response.evidence_id, first.response.evidence_id); + samples.push({ + round, + elapsed_ms: elapsedMs, + structured_reply_bytes: Buffer.byteLength(serialize(result.response)), + full_tool_reply_bytes: Buffer.byteLength(serialize(result.wire_response)), + }); + } + const trace = first.response.normalized_result; + const addons = trace.graph.nodes.filter( + ({ kind }) => kind === "native-addon", + ); + assert.equal( + addons.length, + 1, + "The selected native add-on must remain visible", + ); + const addon = addons[0]; + const unavailable = addon.observations.some( + ({ properties }) => properties.availability === "unavailable", + ); + const expectedLinks = [ + sourceId, + ...(companionSha256 === null ? [] : [native.evidence_id]), + ].sort(); + const safeIdentity = + serialize(trace.evidence_links) === serialize(expectedLinks) && + (companionSha256 === null + ? unavailable && + addon.identity.strategy !== "content-digest" && + trace.coverage.status === "partial" && + trace.native_handoffs.length === 0 + : addon.identity.strategy === "content-digest" && + addon.identity.sha256 === companionSha256 && + trace.native_handoffs.length === 1 && + trace.native_handoffs[0].artifact_sha256 === companionSha256 && + trace.native_handoffs[0].status === "evidence-linked" && + serialize(trace.native_handoffs[0].evidence_ids) === + serialize([native.evidence_id])); + let cliParity = null; + if (values["cli-parity"]) { + temporary = await PrivateRuntimeRoot.create({ + prefix: "rea-native-handoff-verifier-", + }); + const bundlePath = join(temporary.path, "bundle.json"); + const exported = await client.callTool({ + name: "export_evidence_bundle", + arguments: { path: bundlePath }, + }); + assert.notEqual(exported.isError, true, serialize(exported)); + const bundle = JSON.parse(await readFile(bundlePath, "utf8")); + const parent = bundle.records.find( + ({ evidence_id }) => evidence_id === sourceId, + ); + assert.ok(parent, "Export omitted the retained parent"); + const request = join(temporary.path, "trace.json"); + // The CLI's portable file contract takes complete Evidence; MCP resolves a + // same-session reference into the same shared application workflow. + await writeFile(request, serialize({ ...query, application: parent })); + const result = await ownedCommand( + process.execPath, + [ + fileURLToPath(new URL("../../rea.mjs", import.meta.url)), + "trace-application-feature", + request, + "--json", + ], + 120_000, + ); + const evidence = JSON.parse(result.stdout.text); + assert.deepEqual(evidence.normalized_result, trace); + assert.equal(evidence.evidence_id, first.response.evidence_id); + cliParity = { + exact_cli_mcp_trace_parity: true, + evidence_id: evidence.evidence_id, + }; + } + let tokenCosts = null; + if (values["tokenizer-python"]) { + temporary ??= await PrivateRuntimeRoot.create({ + prefix: "rea-native-handoff-verifier-", + }); + const input = join(temporary.path, "token-input.json"); + await writeFile( + input, + serialize({ catalog, selected, preparation, query: first }), + ); + const result = await ownedCommand(values["tokenizer-python"], [ + "-c", + `import json,sys,tiktoken +with open(sys.argv[1],encoding='utf-8') as f:d=json.load(f) +enc=tiktoken.get_encoding('cl100k_base') +def n(v):return len(enc.encode(json.dumps(v,ensure_ascii=False,separators=(',',':')),disallowed_special=())) +requests=n(d['preparation']['request'])+n(d['query']['request']) +structured=n(d['preparation']['response'])+n(d['query']['response']) +full_reply=n(d['preparation']['wire_response'])+n(d['query']['wire_response']) +s=n(d['selected']);a=n(d['catalog']) +print(json.dumps(dict(tokenizer='cl100k_base',tiktoken_version=tiktoken.__version__,selected_catalog=s,full_catalog=a,requests=requests,structured_replies=structured,full_tool_replies=full_reply,selected_catalog_structured_task=s+requests+structured,full_catalog_structured_task=a+requests+structured,selected_catalog_full_reply_task=s+requests+full_reply,full_catalog_full_reply_task=a+requests+full_reply)))`, + input, + ]); + tokenCosts = JSON.parse(result.stdout.text); + } + report = { + scope: + "Real shipped-ASAR native identity through public CLI/stdio MCP; simulated native Evidence only, no provider/application executed", + artifact: { path: values.artifact, sha256: archiveSha256 }, + addon_path: values["addon-path"], + companion_sha256_observed: companionSha256, + prior_native_sha256_supplied: values["native-sha256"], + source_paths: values["source-path"] ?? null, + parent_evidence_id: sourceId, + trace_evidence_id: first.response.evidence_id, + preparation_ms: preparationMs, + samples, + safe_identity: safeIdentity, + node: { + node_id: addon.node_id, + identity: addon.identity, + unavailable_observed: unavailable, + basis: addon.observations.map(({ evidence }) => ({ + authority: evidence.authority, + state: evidence.state, + confidence: evidence.confidence, + artifact: evidence.artifact, + location: evidence.location, + })), + }, + native_handoffs: trace.native_handoffs, + evidence_links: trace.evidence_links, + source_coverage: trace.coverage, + graph_coverage: trace.graph.coverage, + facts_sha256: digest({ + node: addon, + native_handoffs: trace.native_handoffs, + evidence_links: trace.evidence_links, + coverage: trace.coverage, + }), + cli_parity: cliParity, + token_costs: tokenCosts, + conditions: + "Fresh MCP session, one selected analysis followed by repeated identical retained traces. Filesystem/build caches uncontrolled. Task token counts include one analysis summary and one trace with name/description/inputSchema catalog projections; structured and complete-tool-reply projections are alternatives, not billing. Repeated rounds, export, CLI parity and tokenizer are validation overhead outside that one-task token count but inside total resource receipts. A wrong-identity baseline is not a successful equivalent task or efficiency credit.", + }; + if (values["require-safe"]) + assert.ok(safeIdentity, "Native identity/linkage oracle failed"); +} catch (cause) { + primaryError = cause; + throw cause; +} finally { + await closeVerifierResources( + [ + () => client.close(), + () => transport.close(), + async () => { + if (retainTemporary) + throw new Error( + `Command cleanup incomplete; retaining owned inputs at ${temporary?.path}.`, + ); + await temporary?.close(); + }, + ], + primaryError, + ); +} +console.log(serialize(report)); diff --git a/src/domain/javascript/javascriptNativeHandoff.ts b/src/domain/javascript/javascriptNativeHandoff.ts index 7d2f7dfb0..00b7c699e 100644 --- a/src/domain/javascript/javascriptNativeHandoff.ts +++ b/src/domain/javascript/javascriptNativeHandoff.ts @@ -14,15 +14,21 @@ export const buildJavaScriptNativeHandoffs = ( edges: readonly ApplicationEdge[], nativeEvidence: readonly Evidence[], ): NativeHandoff[] => { - const nodeById = new Map(nodes.map((node) => [node.node_id, node])); + let nodeById: Map | undefined; return nodes .filter(({ kind }) => kind === "native-addon") .flatMap((node) => { - const digest = artifactDigest(node); - if (digest === null) return []; + // Observations may describe only a containing archive. A native target + // requires the node owner's exact byte identity, independently of its + // path, availability labels or the artifact supporting an observation. + if (node.identity.strategy !== "content-digest") return []; + const digest = node.identity.sha256; const linked = nativeEvidence.filter( ({ subject }) => subject?.digest.sha256 === digest, ); + nodeById ??= new Map( + nodes.map((candidate) => [candidate.node_id, candidate]), + ); const exportNodes = nativeExportNodes(node, edges, nodeById); return [ { @@ -54,16 +60,6 @@ export const buildJavaScriptNativeHandoffs = ( ); }; -const artifactDigest = (node: ApplicationNode): string | null => { - if (node.identity.strategy === "content-digest") return node.identity.sha256; - const observation = node.observations.find( - ({ evidence }) => evidence.artifact.available, - ); - return observation?.evidence.artifact.available === true - ? observation.evidence.artifact.sha256 - : null; -}; - const artifactPath = (node: ApplicationNode): string | null => { for (const { evidence } of node.observations) if ( diff --git a/tests/boundary/mcp/javascriptNativeHandoffIdentity.test.ts b/tests/boundary/mcp/javascriptNativeHandoffIdentity.test.ts new file mode 100644 index 000000000..851a28397 --- /dev/null +++ b/tests/boundary/mcp/javascriptNativeHandoffIdentity.test.ts @@ -0,0 +1,223 @@ +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { createHash } from "node:crypto"; +import { readFile, rm, writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { afterAll, beforeAll, expect } from "vitest"; + +import { toolContract } from "../../../src/contracts/toolContracts.js"; +import { analysisViewResultSchema } from "../../../src/domain/analysisView/analysisView.js"; +import { createEvidence } from "../../../src/domain/evidence.js"; +import { + applicationFeatureTraceResultSchema, + type ApplicationFeatureTraceResult, +} from "../../../src/domain/javascript/javascriptFeatureTraceSchemas.js"; +import { createStrippedAsarAddon } from "../../fixtures/strippedAsarAddon.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; + +const client = new Client({ name: "native-handoff-identity", version: "1" }); +const transport = new StdioClientTransport({ + command: process.execPath, + args: [resolve("scripts/rea.mjs"), "mcp"], + cwd: process.cwd(), + env: { PATH: process.env.PATH ?? "" }, + stderr: "pipe", +}); +beforeAll(async () => { + await client.connect(transport); + transport.stderr?.on("data", () => undefined); +}); +afterAll(async () => { + await Promise.all([client.close(), transport.close()]); +}); + +const sha256 = (value: Uint8Array) => + createHash("sha256").update(value).digest("hex"); +const nativeObservation = (path: string, sha256: string) => + createEvidence( + { path, sha256, format: "raw-binary" }, + { id: "ghidra", name: "Ghidra", version: "simulated" }, + { + operation: "binary_overview", + parameters: {}, + result: { + summary: "Simulated native Evidence for subject identity only.", + }, + limitations: ["No native provider executed."], + }, + ); + +interface ExpectedIdentity { + readonly available: boolean; + readonly addon: string; + readonly archiveDigest: string; + readonly nativeDigest: string; + readonly parentId: string; + readonly nativeId: string; + readonly linked: boolean; +} + +const expectHandoffIdentity = ( + trace: ApplicationFeatureTraceResult, + expected: ExpectedIdentity, +) => { + const node = trace.graph?.nodes.find(({ kind }) => kind === "native-addon"); + expect(node).toBeDefined(); + expect(trace.evidence_links).toEqual( + [ + expected.parentId, + ...(expected.available && expected.linked ? [expected.nativeId] : []), + ].sort(), + ); + if (expected.available) { + expect(node?.identity).toMatchObject({ + strategy: "content-digest", + sha256: expected.nativeDigest, + }); + expect(trace.native_handoffs).toHaveLength(1); + expect(trace.native_handoffs[0]).toMatchObject({ + native_node_id: node?.node_id, + artifact_sha256: expected.nativeDigest, + artifact_path: expected.addon, + status: expected.linked + ? "evidence-linked" + : "requires-provider-analysis", + evidence_ids: expected.linked ? [expected.nativeId] : [], + }); + } else { + expect(node?.identity).toMatchObject({ + strategy: "canonical-path", + artifact_sha256: expected.archiveDigest, + path: expected.addon, + }); + expect(node?.observations).toContainEqual( + expect.objectContaining({ + properties: expect.objectContaining({ + availability: "unavailable", + hash_status: "unavailable", + inventory_artifact_id: null, + }), + }), + ); + expect(trace.coverage.status).toBe("partial"); + expect(trace.graph?.coverage.status).toBe("partial"); + expect(trace.native_handoffs).toEqual([]); + expect(trace.summary.native_handoffs).toBe(0); + } +}; + +for (const available of [false, true]) { + cliTest( + `keeps native handoff byte identity ${available ? "exact when available" : "unknown for a missing ASAR companion"}`, + async ({ cli }) => { + const { root, archive, addon, original } = + await createStrippedAsarAddon(); + const companion = join(`${archive}.unpacked`, addon); + if (available) await writeFile(companion, original); + else await rm(companion); + const archiveDigest = sha256(await readFile(archive)); + const nativeDigest = sha256(original); + const containerEvidence = nativeObservation(archive, archiveDigest); + const nativeEvidence = nativeObservation(companion, nativeDigest); + const analysis = await cli.run({ + arguments: ["analyze-javascript-application", archive, "--json"], + }); + expect(analysis.exitCode, analysis.stderr).toBe(0); + const parent = toolContract( + "analyze_javascript_application", + ).outputSchema.parse(analysis.json); + const summary = await client.callTool({ + name: "analyze_javascript_application", + arguments: { input_path: archive, detail: "summary" }, + }); + expect(summary.isError).not.toBe(true); + const summaryEvidence = toolContract( + "analyze_javascript_application", + ).outputSchema.parse(summary.structuredContent); + expect( + analysisViewResultSchema.parse(summaryEvidence.normalized_result) + .parent_evidence_id, + ).toBe(parent.evidence_id); + const observed: { + trace: ApplicationFeatureTraceResult; + linked: boolean; + }[] = []; + for (const native_observations of [ + [containerEvidence, nativeEvidence], + [], + ]) { + const query = { + seed: { + kind: "string", + value: addon, + match: "exact", + case_sensitive: true, + }, + direction: "outgoing", + native_observations, + }; + const request = join( + root, + `trace-${String(native_observations.length)}.json`, + ); + await writeFile( + request, + JSON.stringify({ + ...query, + application: parent, + }), + ); + const cliTrace = await cli.run({ + arguments: ["trace-application-feature", request, "--json"], + }); + expect( + cliTrace.exitCode, + cliTrace.exitCode === 0 + ? undefined + : JSON.stringify(cliTrace.json ?? cliTrace.stderr), + ).toBe(0); + const cliEvidence = toolContract( + "trace_application_feature", + ).outputSchema.parse(cliTrace.json); + const response = await client.callTool({ + name: "trace_application_feature", + arguments: { + ...query, + application: { + kind: "retained-evidence", + evidence_id: parent.evidence_id, + }, + }, + }); + expect(response.isError).not.toBe(true); + const evidence = toolContract( + "trace_application_feature", + ).outputSchema.parse(response.structuredContent); + expect(evidence.evidence_id).toBe(cliEvidence.evidence_id); + for (const value of [cliEvidence, evidence]) + observed.push({ + trace: applicationFeatureTraceResultSchema.parse( + value.normalized_result, + ), + linked: native_observations.length > 0, + }); + } + // Exercise both provider-evidence selections and adapters before checking + // the missing-byte outcome; a failed baseline still observes both paths. + for (const { trace, linked } of observed) + expectHandoffIdentity(trace, { + available, + addon, + archiveDigest, + nativeDigest, + parentId: parent.evidence_id, + nativeId: nativeEvidence.evidence_id, + linked, + }); + expect(sha256(await readFile(archive))).toBe(archiveDigest); + if (available) + expect(sha256(await readFile(companion))).toBe(nativeDigest); + await client.ping(); + }, + ); +}