diff --git a/docs/javascript-application-workflows.md b/docs/javascript-application-workflows.md index ef5f12c67a..6134a4c3ff 100644 --- a/docs/javascript-application-workflows.md +++ b/docs/javascript-application-workflows.md @@ -168,6 +168,9 @@ valid inputs. Endpoint nodes keep the exact endpoint in each observation's `properties.value`; semantic request and response nodes keep it in `properties.endpoint`. Endpoint +lookup identities are preserved independently of the primitive-value lattice's +byte budget. An oversized normalized literal can become unknown while the same +string remains an exact endpoint for tracing and comparison. Endpoint identity keys use `value-sha256:` over the canonical JSON string when that is shorter, or when the original value starts with the reserved digest prefix. Other short endpoint keys remain unchanged. Endpoint labels use the diff --git a/docs/javascript-artifact-reconstruction.md b/docs/javascript-artifact-reconstruction.md index ac87b51834..c8e368e398 100644 --- a/docs/javascript-artifact-reconstruction.md +++ b/docs/javascript-artifact-reconstruction.md @@ -437,7 +437,18 @@ retained candidate strings and temporary canonical-key serialization. This is an in-memory semantic allocation bound independent of CLI or MCP transport budgets. Source literal bytes remain in the parsed source and artifact evidence; when a normalized semantic value exceeds the bound, its value is unknown and -coverage is partial. Expression +coverage is partial. Direct string literals, cooked templates without +substitutions, and derived strings all use this normalization boundary. A +single exact primitive is validated without allocating deduplication or sort +state or serializing a canonical key. Configuration defaults use the same +normalizer; limited defaults retain `value_resource_limit` and an anchored +resource-limit unknown. Known null, false, zero, and empty-string defaults stay +exact. This primitive-value bound does not truncate lookup identities such as +source paths, field names or request endpoints. Full endpoint properties remain +available for endpoint queries, tracing and comparison. Each analysis records encountered limit classifications independently of +later projections: a selected healthy sibling can remain exact while whole-file +coverage reports the encountered bound. Projection preserves limiting values +from the shared property-slot owner. Expression evaluation and provenance walks stop after 256 nested levels. Union products and string lengths are checked before allocating combinations or concatenated strings. When a boundary is reached, the value stays unknown, semantic coverage diff --git a/docs/verification/javascript-literal-byte-budget-2026-10-15.json b/docs/verification/javascript-literal-byte-budget-2026-10-15.json new file mode 100644 index 0000000000..f2d9d8eba1 --- /dev/null +++ b/docs/verification/javascript-literal-byte-budget-2026-10-15.json @@ -0,0 +1,547 @@ +{ + "base_revision": "e9a71efa31d6c3675b67938b8a79fa4ddf01604c", + "scope": "Semantic per-value string-byte normalization; real static shipped-application control, no hosted-model performance claim", + "runtime": { + "platform": "Linux x64", + "node": "24.18.0", + "npm": "11.16.0", + "tiktoken": "0.12.0" + }, + "baseline_source_sha256": { + "src/domain/javascript/javascriptSemanticValues.ts": "df3fc30b5ba91901a85fe56f200355930da32d054bb026a747e3414af233b6b5", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "c12b29f0faf4b6511777cba7ed8ac007c6d6170d657f01e43e194feacb3e0601", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0203d207e0d6bb8aaa17746090f008876a11653afd660fce3dc44f3fda646a6c", + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1" + }, + "final_source_sha256": { + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1", + "src/application/javascript/JavaScriptSemanticGraphDataProjection.ts": "844d4639401a9f050ce42c80192572a6d0d74fe4f81b37e93a2c20aaca9fb9a6", + "src/domain/javascript/javascriptSemanticAnalysis.ts": "a3f802ca3446c2b5376cc58b19d818eff318e6775bd47752793c3f649994f8a2", + "src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts": "2e78717d30e637b5fc5bab6d0eff063f7a62e0199d33697c1cdfaf8317baa698", + "src/domain/javascript/javascriptSemanticDataEffectHelpers.ts": "8f9cf1a36f05b03dd816eaf9a6cdba0160d34ad7d17a961206e1bc32444d2ccc", + "src/domain/javascript/javascriptSemanticDataEffects.ts": "e69408f2f5910a0107fd5d0cae0ff60ed706448857bc8e535c37cac1a0cd4d37", + "src/domain/javascript/javascriptSemanticIr.ts": "68dad360d0a9e6bd72108b2717cae756b84ca1efb48455248ded2ff699d2c113", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "899eeb6ccdf666f504f8b722327599997c924c8e4fb13a1cbd13edaf08c96640", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0993a397f2a2a48c2b6c35494e940b3fce5c2c9032cfe33c7cf0401128c79dde", + "src/domain/javascript/javascriptSemanticState.ts": "bfcface5dcda22594f47ed390efa15f9140fe41cb2d5995896e52dd0ee31b978", + "src/domain/javascript/javascriptSemanticValues.ts": "a4ba51cc60fff38ef11fd5af35a2e1ad753cdb2dea9d44895f91d89320c5ddeb", + "tests/acceptance/applications/javascriptEndpointPayload.test.ts": "b3cfcde1c5b5e0698f84afd51f4b53e42e56101267707c20c0d038320f5cfbd2", + "tests/boundary/mcp/javascriptLiteralBudget.test.ts": "070106115b482269794799e448bd20b8e02df1f511f4ceb2b8fa75612c7f78e4", + "tests/boundary/mcp/javascriptLiteralPayload.test.ts": "03618272c605f8877ed171ab39e037e4d80eac72b7478421640076f739adfb50" + }, + "artifact": { + "path": "/tmp/rea-codemode-experiment-20261015/artifacts/obsidian-1.14.4.asar", + "sha256": "146ef8470d8cbb4a6db209bdc19bf2bad13350444186c0fd93c31087b142b92f" + }, + "selected_source_paths": ["package.json", "main.js"], + "where": { + "path": "main.js", + "kind": "javascript-module" + }, + "identical_parent_evidence_id": "ev_73ef06e3b9b944f54c5257f7960279127d8518d2a73cd8618bd045dbe44e6b5a", + "cli_parity": { + "evidence_id": "ev_b3586dfdf7d4f01157f8f2978326aaa7103987110f2df4fa5bf3331ff2898749", + "facts_sha256": "d3be5e91d2ec3fd9a95851cfbbdef9c168bb4c7153f78810b8b235e005401299", + "exact_cli_mcp_view_parity": true + }, + "unchanged_query_shapes": { + "legacy": { + "calls": 24, + "structured_reply_bytes": 325201, + "facts_sha256": "d3be5e91d2ec3fd9a95851cfbbdef9c168bb4c7153f78810b8b235e005401299", + "matching_modules": 23, + "coverage": { + "status": "page", + "examined": 55, + "total": 55, + "next_offset": null, + "exhausted": true + } + }, + "references": { + "calls": 1, + "structured_reply_bytes": 22158, + "facts_sha256": "d3be5e91d2ec3fd9a95851cfbbdef9c168bb4c7153f78810b8b235e005401299", + "matching_modules": 23, + "coverage": { + "status": "page", + "examined": 23, + "total": 23, + "next_offset": null, + "exhausted": true + } + } + }, + "unchanged_token_costs": { + "tokenizer": "cl100k_base", + "tiktoken_version": "0.12.0", + "costs": { + "legacy": { + "selected_catalog": 7977, + "full_catalog": 195522, + "common_request": 53, + "common_summary": 2675, + "requests": 2836, + "structured_replies": 71761, + "selected_catalog_task_total": 85302, + "full_catalog_task_total": 272847 + }, + "references": { + "selected_catalog": 7977, + "full_catalog": 195522, + "common_request": 53, + "common_summary": 2675, + "requests": 102, + "structured_replies": 5741, + "selected_catalog_task_total": 16548, + "full_catalog_task_total": 204093 + } + } + }, + "cohorts": { + "baseline": { + "preparation_ms": 19089.119622, + "query_samples": [ + { + "round": 0, + "mode": "legacy", + "elapsed_ms": 383.8260569999984 + }, + { + "round": 0, + "mode": "references", + "elapsed_ms": 15.778886999996757 + }, + { + "round": 1, + "mode": "references", + "elapsed_ms": 13.15173700000014 + }, + { + "round": 1, + "mode": "legacy", + "elapsed_ms": 240.44363500000327 + }, + { + "round": 2, + "mode": "legacy", + "elapsed_ms": 346.04796499999793 + }, + { + "round": 2, + "mode": "references", + "elapsed_ms": 13.727306999997381 + } + ], + "resources": { + "elapsed_seconds": 33.394, + "cpu_seconds": 33.016802, + "peak_cgroup_bytes": 1979863040, + "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": { + "preparation_ms": 19061.292544, + "query_samples": [ + { + "round": 0, + "mode": "legacy", + "elapsed_ms": 344.6316079999997 + }, + { + "round": 0, + "mode": "references", + "elapsed_ms": 14.438351000000694 + }, + { + "round": 1, + "mode": "references", + "elapsed_ms": 11.38763100000142 + }, + { + "round": 1, + "mode": "legacy", + "elapsed_ms": 298.41579600000114 + }, + { + "round": 2, + "mode": "legacy", + "elapsed_ms": 211.45682099999976 + }, + { + "round": 2, + "mode": "references", + "elapsed_ms": 10.275370999999723 + } + ], + "resources": { + "elapsed_seconds": 32.949, + "cpu_seconds": 32.559187, + "peak_cgroup_bytes": 1880096768, + "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 + } + } + } + } + }, + "conditions": "First query is after shared artifact analysis; subsequent queries share one MCP session. Legacy item reads are serial; a concurrent legacy baseline is unmeasured. Filesystem/build warmness uncontrolled. Token costs count one post-change catalog, preparation request/summary and each request/structured reply once; client rendering, replay, reasoning, final answer and model latency are unmeasured. Preparation and parity/tokenization overhead are outside per-query elapsed times; external family-resource receipts include the complete verifier and children. Before/after used separate fresh MCP sessions. CPU/memory includes analysis, query arms, export, CLI parity and tokenization; cgroup memory includes file cache, not summed RSS. Counters are at payload exit while observer lives, not final scope counters. One before/after control establishes no speedup or equivalence bound. Catalog BPE projection excludes annotations/output schemas.", + "resource_policy": { + "memory_max_bytes": 8589934592, + "early_stop_bytes": 6442450944, + "cpu_quota_percent": 100, + "swap": false + }, + "observer_sources_sha256": { + "resource-run.py": "7728862171415e7dee2c11af2ead2b9a54265fccaf82a7113956d36b29065e33", + "resource_payload.py": "6f089e645459eba80c5d73e39c21606c78173a44daf49fabc3b88c4631ce51c6" + }, + "regressions": { + "original_main": "Quoted/template oversized literals failed through public CLI/MCP; addition/small controls passed. Corrected domain boundary reproduced 2 failures/16 passes.", + "review_head": "8 failed/25 passed before fixes for projection tags, encountered-but-discarded limits and bare quoted/template defaults; includes exact null default control.", + "interim_review_fix": "65/66 focused passed; old doubling-chain expectation omitted a genuine expression-depth limit now observed. Adjusted that assertion to preserve the byte-limit requirement while allowing other observed classes.", + "final_focused": "66 passed across five files on the restored 38d263a runtime. Final corrected public selection passed 7/7 in two files: the six literal/default/member cases plus the augmented original large-endpoint acceptance. 67 distinct focused cases across invocations; not a single 67-case invocation.", + "final_semantic_suite": "932 passed in 36 files on 38d263a. Final production src tree (including these domain tests) verified byte-identical to that revision; no repeated semantic run claimed.", + "arithmetic": "Computed 174762*6+2=1048574 and 174763*6+2=1048580. Corrected the erroneous published 1048578 total.", + "final_grouped_domain": "27 passed after grouping observations/defaults/allocation within function-size rules; no scenarios removed.", + "endpoint_contract_correction": "Restored exact endpoint lookup identities rather than shrinking the existing large-endpoint fixture. Added the identical oversized string as an exported primitive in that acceptance workflow: literal normalization must report its bound while full endpoint lookup/feature trace/semantic trace/version comparison continue to work. Production src tree restored byte-identical to 38d263a; 932 semantic tests from that state remain applicable. Final corrected public tests/gates recorded after execution.", + "final_corrected_gates": "Full static gate and docs gate (5 consistency tests) passed after endpoint identity restoration and strengthened unchanged-large-input acceptance. Production src remains byte-identical to 38d263a; final public 7/7 and fresh real control passed." + }, + "prior_attempts": [ + { + "stage": "Initial literal normalization, incomplete for member/default paths identified in review", + "revision": "40b9d3c5b9cbeae7661d8e351e1b5ef6e5338931", + "source_sha256": { + "src/domain/javascript/javascriptSemanticValues.ts": "2debbb4f32d7d189819f2413f0f7d05b9bdcf497451a1c578475e3f2ec6c8554", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "899eeb6ccdf666f504f8b722327599997c924c8e4fb13a1cbd13edaf08c96640", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0203d207e0d6bb8aaa17746090f008876a11653afd660fce3dc44f3fda646a6c", + "src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts": "747d893d6ddd91cf24d3b62b7eb50e0af9b1715ab355bbfc2e10ed0dc171ec17", + "tests/boundary/mcp/javascriptLiteralBudget.test.ts": "46ceb5a169de3eec3a335b05ef4aa7b1b341a5ac06ad325bd191e862c5b6be70", + "tests/boundary/mcp/javascriptLiteralPayload.test.ts": "03618272c605f8877ed171ab39e037e4d80eac72b7478421640076f739adfb50", + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1" + }, + "real_control_cohort": { + "preparation_ms": 18876.764036, + "query_samples": [ + { + "round": 0, + "mode": "legacy", + "elapsed_ms": 346.7870870000006 + }, + { + "round": 0, + "mode": "references", + "elapsed_ms": 18.34765800000241 + }, + { + "round": 1, + "mode": "references", + "elapsed_ms": 11.56626999999935 + }, + { + "round": 1, + "mode": "legacy", + "elapsed_ms": 227.17703500000061 + }, + { + "round": 2, + "mode": "legacy", + "elapsed_ms": 251.5148330000011 + }, + { + "round": 2, + "mode": "references", + "elapsed_ms": 10.888227999999799 + } + ], + "resources": { + "elapsed_seconds": 31.347, + "cpu_seconds": 31.05517, + "peak_cgroup_bytes": 1589915648, + "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 + } + } + } + }, + "validation": { + "baseline_public": "Quoted and cooked-template cases retained 200000-unit normalized literals and failed; addition and small exact controls passed. Aggregate-capacity test passed.", + "baseline_domain_corrected": "2 over-bound failures, 16 passed. Initial combined-run domain assertions had incorrectly required an empty optional resourceLimits array; corrected before using the domain reproduction as evidence.", + "fixed_focused": "55 passed across five files.", + "semantic_suite": "923 passed across 36 files.", + "capacity_after_fixture_cleanup": "1 passed; all 16 bounded values, aggregate size >= original, typed identities and retained tracing preserved.", + "interim_static_failure": "Typecheck rejected resume() on the new test SDK stderr Stream declaration. Switched to its declared data event, matching existing typed tests; analyzer source unchanged.", + "public_boundary_after_stream_type_fix": "4 passed across actual CLI and stdio MCP; analyzer source unchanged." + } + }, + { + "stage": "Projection/default correction; final review subsequently found unnormalized request endpoints", + "revision": "38d263a369e7853ccfae1c8e0a3bd387f920c7cf", + "source_sha256": { + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1", + "src/application/javascript/JavaScriptSemanticGraphDataProjection.ts": "844d4639401a9f050ce42c80192572a6d0d74fe4f81b37e93a2c20aaca9fb9a6", + "src/domain/javascript/javascriptSemanticAnalysis.ts": "a3f802ca3446c2b5376cc58b19d818eff318e6775bd47752793c3f649994f8a2", + "src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts": "2e78717d30e637b5fc5bab6d0eff063f7a62e0199d33697c1cdfaf8317baa698", + "src/domain/javascript/javascriptSemanticDataEffectHelpers.ts": "8f9cf1a36f05b03dd816eaf9a6cdba0160d34ad7d17a961206e1bc32444d2ccc", + "src/domain/javascript/javascriptSemanticDataEffects.ts": "e69408f2f5910a0107fd5d0cae0ff60ed706448857bc8e535c37cac1a0cd4d37", + "src/domain/javascript/javascriptSemanticIr.ts": "68dad360d0a9e6bd72108b2717cae756b84ca1efb48455248ded2ff699d2c113", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "899eeb6ccdf666f504f8b722327599997c924c8e4fb13a1cbd13edaf08c96640", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0993a397f2a2a48c2b6c35494e940b3fce5c2c9032cfe33c7cf0401128c79dde", + "src/domain/javascript/javascriptSemanticState.ts": "bfcface5dcda22594f47ed390efa15f9140fe41cb2d5995896e52dd0ee31b978", + "src/domain/javascript/javascriptSemanticValues.ts": "a4ba51cc60fff38ef11fd5af35a2e1ad753cdb2dea9d44895f91d89320c5ddeb", + "tests/boundary/mcp/javascriptLiteralBudget.test.ts": "070106115b482269794799e448bd20b8e02df1f511f4ceb2b8fa75612c7f78e4", + "tests/boundary/mcp/javascriptLiteralPayload.test.ts": "03618272c605f8877ed171ab39e037e4d80eac72b7478421640076f739adfb50" + }, + "real_control_cohort": { + "preparation_ms": 19599.643274, + "query_samples": [ + { + "round": 0, + "mode": "legacy", + "elapsed_ms": 396.9265450000021 + }, + { + "round": 0, + "mode": "references", + "elapsed_ms": 15.658120000000054 + }, + { + "round": 1, + "mode": "references", + "elapsed_ms": 12.24905100000251 + }, + { + "round": 1, + "mode": "legacy", + "elapsed_ms": 237.03392900000108 + }, + { + "round": 2, + "mode": "legacy", + "elapsed_ms": 357.78818299999693 + }, + { + "round": 2, + "mode": "references", + "elapsed_ms": 14.37622100000226 + } + ], + "resources": { + "elapsed_seconds": 33.969, + "cpu_seconds": 33.499223, + "peak_cgroup_bytes": 1893015552, + "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 + } + } + } + }, + "validation": { + "original_main": "Quoted/template oversized literals failed through public CLI/MCP; addition/small controls passed. Corrected domain boundary reproduced 2 failures/16 passes.", + "review_head": "8 failed/25 passed before fixes for projection tags, encountered-but-discarded limits and bare quoted/template defaults; includes exact null default control.", + "interim_review_fix": "65/66 focused passed; old doubling-chain expectation omitted a genuine expression-depth limit now observed. Adjusted that assertion to preserve the byte-limit requirement while allowing other observed classes.", + "final_focused": "66 passed in five files.", + "final_semantic_suite": "932 passed in 36 files.", + "arithmetic": "Computed 174762*6+2=1048574 and 174763*6+2=1048580. Corrected the erroneous published 1048578 total.", + "final_grouped_domain": "27 passed after grouping observations/defaults/allocation within function-size rules; no scenarios removed.", + "endpoint_review_baseline": "Production at 38d263a: 7 failed/14 passed across 13 new domain and 8 CLI/MCP cases before endpoint normalization. The later public linked-response case was not in this baseline run. Regression tests were uncommitted; final formatted test hashes are preserved, not claimed as the baseline test bytes.", + "endpoint_selection_attempt": "First six-file selection exited before execution because a signed-zero test filename was mistyped; corrected the exact path. No test result claimed for that attempt." + } + }, + { + "stage": "Rejected endpoint normalization: violated existing full-endpoint lookup/trace contract; reverted after CI acceptance failure", + "revision": "580887209c4fceda432eac4083fac82a0dd78f15", + "source_sha256": { + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1", + "src/application/javascript/JavaScriptSemanticGraphDataProjection.ts": "a648f97e631985edcb5259ff4f1fdf0332dca18b8c10b57e050e36a785c1c524", + "src/domain/javascript/javascriptSemanticAnalysis.requestBudget.test.ts": "8e84ffa70d834f5589d04118f0aaf33b0383b15ea97545ff69c66a74473aca73", + "src/domain/javascript/javascriptSemanticAnalysis.ts": "a3f802ca3446c2b5376cc58b19d818eff318e6775bd47752793c3f649994f8a2", + "src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts": "2e78717d30e637b5fc5bab6d0eff063f7a62e0199d33697c1cdfaf8317baa698", + "src/domain/javascript/javascriptSemanticDataEffectHelpers.ts": "8f9cf1a36f05b03dd816eaf9a6cdba0160d34ad7d17a961206e1bc32444d2ccc", + "src/domain/javascript/javascriptSemanticDataEffects.ts": "eed961e0b1d8a320e76bbba933408e950b4a4a446110401e8cf470f1e74d8315", + "src/domain/javascript/javascriptSemanticIr.ts": "3b2ede73dbcfc74c4014e4d1affcbf00ecccc29a422ed495a7a3f2f517056c9b", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "899eeb6ccdf666f504f8b722327599997c924c8e4fb13a1cbd13edaf08c96640", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0993a397f2a2a48c2b6c35494e940b3fce5c2c9032cfe33c7cf0401128c79dde", + "src/domain/javascript/javascriptSemanticState.ts": "bfcface5dcda22594f47ed390efa15f9140fe41cb2d5995896e52dd0ee31b978", + "src/domain/javascript/javascriptSemanticValues.ts": "a4ba51cc60fff38ef11fd5af35a2e1ad753cdb2dea9d44895f91d89320c5ddeb", + "tests/boundary/mcp/javascriptLiteralBudget.test.ts": "61319ea954de7f256841f43ca9355a1706ce8e93d30f2a8ba851d5e717bd19e7", + "tests/boundary/mcp/javascriptLiteralPayload.test.ts": "03618272c605f8877ed171ab39e037e4d80eac72b7478421640076f739adfb50" + }, + "real_control_cohort": { + "preparation_ms": 19939.160272, + "query_samples": [ + { + "round": 0, + "mode": "legacy", + "elapsed_ms": 348.3974309999976 + }, + { + "round": 0, + "mode": "references", + "elapsed_ms": 13.97052899999835 + }, + { + "round": 1, + "mode": "references", + "elapsed_ms": 12.060379000002285 + }, + { + "round": 1, + "mode": "legacy", + "elapsed_ms": 212.01217800000086 + }, + { + "round": 2, + "mode": "legacy", + "elapsed_ms": 223.73954800000138 + }, + { + "round": 2, + "mode": "references", + "elapsed_ms": 10.450989999997546 + } + ], + "resources": { + "elapsed_seconds": 33.062, + "cpu_seconds": 32.71624, + "peak_cgroup_bytes": 1897406464, + "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 + } + } + } + }, + "validation": { + "original_main": "Quoted/template oversized literals failed through public CLI/MCP; addition/small controls passed. Corrected domain boundary reproduced 2 failures/16 passes.", + "review_head": "8 failed/25 passed before fixes for projection tags, encountered-but-discarded limits and bare quoted/template defaults; includes exact null default control.", + "interim_review_fix": "65/66 focused passed; old doubling-chain expectation omitted a genuine expression-depth limit now observed. Adjusted that assertion to preserve the byte-limit requirement while allowing other observed classes.", + "final_focused": "81 passed in six files; the same CLI/MCP file then passed 9/9 after adding a linked-response case. That follow-up replaces its earlier 8-case coverage; 82 distinct cases across the final six-file selection, not one 82-case invocation.", + "final_semantic_suite": "945 passed in 37 files, overlapping focused domain coverage.", + "arithmetic": "Computed 174762*6+2=1048574 and 174763*6+2=1048580. Corrected the erroneous published 1048578 total.", + "final_grouped_domain": "27 passed after grouping observations/defaults/allocation within function-size rules; no scenarios removed.", + "endpoint_review_baseline": "Production at 38d263a: 7 failed/14 passed across 13 new domain and 8 CLI/MCP cases before endpoint normalization. The later public linked-response case was not in this baseline run. Regression tests were uncommitted; final formatted test hashes are preserved, not claimed as the baseline test bytes.", + "endpoint_selection_attempt": "First six-file selection exited before execution because a signed-zero test filename was mistyped; corrected the exact path. No test result claimed for that attempt.", + "endpoint_typecheck_attempt": "Full static gate found the inferred optional dataField in the public test computed key. Replaced the in-property check with an explicit bare === true discriminant; scenario inputs and runtime implementation unchanged. Corrected static/public checks recorded separately.", + "final_gates": "Full static gate passed after the explicit test discriminant; final public file passed 9/9 again, then docs gate passed including 5 consistency tests. Runtime implementation unchanged after the 945-test semantic suite and real control." + }, + "ci_failure": { + "run_id": 38758262643, + "job_id": 116503076738, + "test": "tests/acceptance/applications/javascriptEndpointPayload.test.ts", + "result": "1 failed/1573 passed/19 skipped; request endpoint no longer matched its complete source value. Other local tests and real Obsidian control passed but did not establish this compatibility contract.", + "aggregate_result": "Merged source/built report: 1 failed/15625 passed/138 skipped. Portable tests and CI required failed as aggregates of the same endpoint assertion; no separate additional failed test." + } + } + ], + "review_baseline_revision": "40b9d3c5b9cbeae7661d8e351e1b5ef6e5338931", + "review_baseline_source_sha256": { + "scripts/verify/javascript/module-references.mjs": "9d5f0b8b35ed2ebb4591f9dc3baea0fc116a0c3fd7d8e5ce30ec476c84671ed1", + "src/application/javascript/JavaScriptSemanticGraphDataProjection.ts": "ee541e634264363822bf4ce05c42c72272029c7e8da72bbeed643ad1341d0756", + "src/domain/javascript/javascriptSemanticAnalysis.ts": "a1cf791a61ee4742c351c006b69b64eba6bef3068f7a8450ed8f9366bfe4ff88", + "src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts": "747d893d6ddd91cf24d3b62b7eb50e0af9b1715ab355bbfc2e10ed0dc171ec17", + "src/domain/javascript/javascriptSemanticDataEffectHelpers.ts": "5ee7af18dd847726d8fe1828ea25f386ff9ef317d996a3f7969253bf73973cfa", + "src/domain/javascript/javascriptSemanticDataEffects.ts": "4c62462016246bc7198294a74a60e87598fc009c53f1601abc2d4662d41b5bd1", + "src/domain/javascript/javascriptSemanticIr.ts": "14955cb4b7912ed6a4f26f804a2480f841dcb6084dbddd15856375fa62cdcfa1", + "src/domain/javascript/javascriptSemanticPrimitives.ts": "899eeb6ccdf666f504f8b722327599997c924c8e4fb13a1cbd13edaf08c96640", + "src/domain/javascript/javascriptSemanticResourceLimits.ts": "0203d207e0d6bb8aaa17746090f008876a11653afd660fce3dc44f3fda646a6c", + "src/domain/javascript/javascriptSemanticState.ts": "d94c5a88bb105284fab192406a8c88860d59f80c93394ed3ee7264969df254a6", + "src/domain/javascript/javascriptSemanticValues.ts": "2debbb4f32d7d189819f2413f0f7d05b9bdcf497451a1c578475e3f2ec6c8554", + "tests/boundary/mcp/javascriptLiteralBudget.test.ts": "46ceb5a169de3eec3a335b05ef4aa7b1b341a5ac06ad325bd191e862c5b6be70", + "tests/boundary/mcp/javascriptLiteralPayload.test.ts": "03618272c605f8877ed171ab39e037e4d80eac72b7478421640076f739adfb50" + }, + "implementation": "Per-analysis state records encountered resource classifications at expression/default normalization; final coverage consumes those facts. Slot projection preserves tagged unknown values. Removed retained-value recursive coverage scan and duplicate raw default reader. Defaults retain optional valueResourceLimit and graph value_resource_limit plus an anchored resource-limit unknown; known null/false/0/empty remain exact. Endpoint identities intentionally remain exact lookup values, outside the normalized primitive lattice; rejected endpoint normalization and its CI failure are preserved separately.", + "restored_production_revision": "38d263a369e7853ccfae1c8e0a3bd387f920c7cf", + "final_corrected_resource_receipts": { + "public_7": { + "elapsed_seconds": 43.621, + "cpu_seconds": 43.097453, + "peak_cgroup_bytes": 1520324608, + "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 + } + } + }, + "static_and_docs": { + "elapsed_seconds": 111.403, + "cpu_seconds": 110.830839, + "peak_cgroup_bytes": 4268244992, + "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 + } + } + } + } +} diff --git a/docs/verification/javascript-literal-byte-budget-2026-10-15.md b/docs/verification/javascript-literal-byte-budget-2026-10-15.md new file mode 100644 index 0000000000..030cfd92eb --- /dev/null +++ b/docs/verification/javascript-literal-byte-budget-2026-10-15.md @@ -0,0 +1,86 @@ +# Semantic literal byte-budget verification + +Tracks #3933 under #4027. All normalized semantic literals and configuration +defaults use the primitive owner. Its singleton path validates strings/nonfinite +numbers/negative zero without a Set, sorting or canonical-key serialization. +Each source analysis owns a bounded set of encountered limit classifications; +coverage consumes these observations after evaluation. This replaces a recursive +scan that only saw retained binding/return values. No persistent cache is added. + +## Correctness and coverage + +Public CLI and actual stdio MCP cases cover quoted strings, cooked templates, +equivalent concatenation, member receivers, a configuration-only expression +statement and a small exact control. Direct/default oversized values become +`resource-limit` unknowns with partial application coverage and +`javascript_semantic_primitive_bytes = 1048576`. Defaults carry +`value_resource_limit` inline and an unknown anchored to their config node. +Known null/false/zero/empty defaults remain exact. Original source/digests and +CLI/MCP identity parity are preserved. + +Member and destructuring projections use the shared slot owner's tagged value. +An encountered limit stays visible even when a subsequent projection selects a +healthy sibling: the selected value remains exactly `7`, while file coverage is +partial. This avoids inferring completeness from the final retained values alone. +The duplicate raw default reader and superseded retained-value scan are removed. + +Boundary controls admit 174,762 UTF-16 units (1,048,574 estimated JSON bytes) +and reject 174,763 (1,048,580 bytes). Actual arithmetic corrected the earlier +published 1,048,578 total; the unit-count boundary assertions were already correct. +The estimate counts six bytes per unit plus two quotes. It does not establish +actual ASCII JSON size above 1 MiB or an observed OOM. + +The initial main baseline reproduced both public literal failures, with addition +and small controls passing. A mistaken optional-limit-array control was corrected +before the separate domain reproduction (2 failed/16 passed). Review head +`40b9d3c5` then reproduced the additional projection/default gaps (8 failed, +25 passed). The first review correction passed 65/66 focused tests; a doubling +chain now truthfully retains an additional encountered expression-depth limit, +so its byte-growth assertion was updated to allow other recorded limits. +Prior validation on the restored runtime: **66 focused tests** in five files and **932 semantic tests** +in 36 files, with overlapping domain coverage. The large-wire fixture retains +16 individually admitted values and at least the former aggregate UTF-16 payload, +plus complete CLI/MCP delivery, typed identities, empty strings and retained +tracing. Inputs regenerate from the tests. + +## Endpoint identity compatibility + +An attempted endpoint normalization at `5808872` passed its new local cases and real Obsidian control, but failed the existing CI endpoint-payload acceptance workflow (1 failed/1,573 passed/19 skipped in that shard). It violated the established full endpoint lookup/trace contract. This extension was reverted; endpoint values are lookup identities, outside the normalized primitive lattice. The original large endpoint is preserved. Corrected public validation passed **7/7** across two files; this adds one acceptance workflow to the previous 66-case focused selection (**67 distinct cases**, not one 67-case invocation). Its acceptance workflow now also normalizes that identical string as an exported primitive, requiring the typed value bound while endpoint lookup, both trace workflows and version comparison remain usable. Prior measurements and failed attempts remain in the JSON record. Production source is restored byte-identical to `38d263a`; the 932-test semantic run belongs to that state. Final full static and documentation gates (five consistency tests) passed after the compatibility correction. + +## Real application control and costs + +Official Obsidian 1.14.4 ASAR, selected `package.json` and `main.js`. Follow the +acquisition/digest and existing verifier command in +[module-reference verification](javascript-module-references-2026-10-15.md). +Use three alternating-order query pairs with CLI parity on each revision. +Application code is not executed. + +The final correction retains the exact baseline full parent/view Evidence IDs, +23-row fact digest, source coverage and unknowns. Static `cl100k_base` task costs +remain 16,548 with selected name/description/inputSchema contracts, or 204,093 +with the full catalog projection, for the reference workflow. Serial legacy +reads also keep their original costs. The query projection improvement belongs +to #4097; these are controls for semantic normalization and coverage. + +| Complete real verifier | Before | Final correction | +| ---------------------- | ------------: | ---------------: | +| Elapsed seconds | 33.394 | 32.949 | +| CPU seconds | 33.016802 | 32.559187 | +| Peak cgroup bytes | 1,979,863,040 | 1,880,096,768 | + +Includes initial analysis, both query arms, export, CLI parity and tokenizer. +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. Fresh MCP sessions do not establish OS-cold +cache conditions. Shared-host scheduling and filesystem/build warmness were +uncontrolled. This one before/after control establishes neither a speedup nor +an equivalence bound. Static BPE excludes annotations/output schemas, client +rendering/replay, reasoning and final answers; no hosted agent was run. Linux +static ASAR analysis was exercised, not live Electron or other platforms. + +[Compact records, samples, source hashes and prior attempts](javascript-literal-byte-budget-2026-10-15.json) +preserve the initial incomplete correction separately from the final measurements. +No downloadable application or complete bundle is archived. Reproduce boundary +outcomes through the listed public tests and reuse the existing real verifier +and preserved 8 GiB observer linked above. Observer hashes and result-affecting +touched source hashes are pinned in the records. diff --git a/src/application/javascript/JavaScriptSemanticGraphDataProjection.ts b/src/application/javascript/JavaScriptSemanticGraphDataProjection.ts index 20506946df..01e739e859 100644 --- a/src/application/javascript/JavaScriptSemanticGraphDataProjection.ts +++ b/src/application/javascript/JavaScriptSemanticGraphDataProjection.ts @@ -12,6 +12,7 @@ import { } from "./JavaScriptSemanticGraphConstruction.js"; import { unknownSemanticEvidence } from "./JavaScriptSemanticGraphEvidence.js"; import type { SemanticFlowProjectionContext } from "./JavaScriptSemanticGraphFlowProjection.js"; +import { semanticResourceLimitReason } from "../../domain/javascript/javascriptSemanticResourceLimits.js"; /** Project environment, argv, file, default, and precedence candidates. */ export const projectSemanticConfiguration = ( @@ -56,7 +57,17 @@ export const projectSemanticConfiguration = ( ...(operation.kind === "default" ? (["overrides"] as const) : []), ], location: operation.location, - detail: `Static ${operation.kind} configuration identity is partial.`, + ...(operation.valueResourceLimit === undefined + ? { + detail: + operation.kind === "default" + ? "Static default configuration value is partial." + : `Static ${operation.kind} configuration identity is partial.`, + } + : { + reason: "resource-limit", + detail: semanticResourceLimitReason(operation.valueResourceLimit), + }), candidateNodeIds: [], }); } @@ -80,6 +91,9 @@ const addConfigurationNode = ( key: operation.key, source_kind: operation.kind, value: operation.value, + ...(operation.valueResourceLimit === undefined + ? {} + : { value_resource_limit: operation.valueResourceLimit }), }, }); @@ -279,6 +293,7 @@ const bindingOrOwner = ( : (context.callableNodes.get(callableId) ?? context.moduleNode)); interface DataUnknownInput { + readonly reason?: "ambiguous-target" | "resource-limit"; readonly context: SemanticFlowProjectionContext; readonly node: JavaScriptSemanticGraphNode; readonly family: "boundary" | "configuration" | "request"; @@ -317,7 +332,7 @@ const addDataUnknown = (input: DataUnknownInput): void => { node_id: node.node_id, family, relation_kinds: relationKinds, - reason: "ambiguous-target", + reason: input.reason ?? "ambiguous-target", detail, candidate_node_ids: candidateNodeIds, evidence: unknownSemanticEvidence(context.file, location), diff --git a/src/domain/javascript/javascriptSemanticAnalysis.ts b/src/domain/javascript/javascriptSemanticAnalysis.ts index 5d63b7699a..176be51dda 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.ts @@ -62,7 +62,6 @@ import { } from "./javascriptElectronMemberWrites.js"; import { range } from "./javascriptStaticAnalysisHelpers.js"; import { semanticCoverage } from "./javascriptSemanticCoverage.js"; -import { semanticResourceLimitsIn } from "./javascriptSemanticResourceLimits.js"; import { parseJavaScriptSource, type ParsedJavaScriptSource, @@ -197,12 +196,7 @@ export function* analyzeParsedJavaScriptSemanticsSteps( ...derived, coverage: semanticCoverage( parserPartial, - semanticResourceLimitsIn([ - ...bindings.map(({ value }) => value), - ...callables.flatMap((callable) => - callable.returnSites.map(({ value }) => value), - ), - ]), + [...state.valueResourceLimits].sort(), ), limitations: [ ...(parserPartial @@ -698,6 +692,7 @@ const createState = ( return { program, staticImportResolutionMode, + valueResourceLimits: new Set(), parentsByNode: new WeakMap(), scopes: [root], scopesById: new Map([[root.scopeId, root]]), diff --git a/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts index 9e02cdb00b..21ef6c2105 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.unions.test.ts @@ -219,7 +219,120 @@ describe("logical primitive unions", () => { }); }); +describe("semantic primitive resource observations", () => { + it.each([ + { + name: "member", + source: (large: string) => + `const answer = (${JSON.stringify(large)}).field;`, + }, + { + name: "destructuring", + source: (large: string) => + `const { nested: { answer } } = { nested: ${JSON.stringify(large)} };`, + }, + ])("preserves a limiting value through $name projection", ({ source }) => { + const ir = analyzeJavaScriptSemantics(source("x".repeat(200_000))); + expect(topLevelBinding(ir, "answer").value).toMatchObject({ + status: "unknown", + resourceLimit: "primitive-bytes", + }); + expect(ir.coverage).toMatchObject({ + status: "partial", + resourceLimits: ["primitive-bytes"], + }); + }); + + it("records an encountered bound without weakening a selected healthy sibling", () => { + const ir = analyzeJavaScriptSemantics( + `const answer = ({ ignored: ${JSON.stringify("x".repeat(200_000))}, kept: 7 }).kept;`, + ); + expect(topLevelBinding(ir, "answer").value).toEqual({ + status: "literal", + value: 7, + }); + expect(ir.coverage).toMatchObject({ + status: "partial", + resourceLimits: ["primitive-bytes"], + }); + }); +}); + +describe("configuration default normalization", () => { + it.each(["quoted", "template"])( + "normalizes a bare %s configuration default", + (syntax) => { + const large = "x".repeat(200_000); + const expression = + syntax === "quoted" ? JSON.stringify(large) : `\`${large}\``; + const ir = analyzeJavaScriptSemantics( + `process.env.MODE ?? ${expression};`, + ); + const operation = ir.configurationOperations.find( + ({ kind }) => kind === "default", + ); + expect( + typeof operation?.value === "string" ? operation.value.length : 0, + ).toBeLessThanOrEqual(174_762); + expect(operation).toMatchObject({ + value: null, + resolution: "partial", + valueResourceLimit: "primitive-bytes", + }); + expect(ir.coverage).toMatchObject({ + status: "partial", + resourceLimits: ["primitive-bytes"], + }); + }, + ); + + it.each([null, false, 0, ""])( + "keeps an exact bounded configuration default %j", + (value) => { + const ir = analyzeJavaScriptSemantics( + `process.env.MODE ?? ${JSON.stringify(value)};`, + ); + expect( + ir.configurationOperations.find(({ kind }) => kind === "default"), + ).toMatchObject({ value, resolution: "complete" }); + expect(ir.coverage.resourceLimits ?? []).toEqual([]); + }, + ); +}); + describe("semantic primitive allocation and binding reuse", () => { + it.each(["quoted", "template"])( + "applies the normalized byte boundary to %s literals", + (syntax) => { + // Six estimated JSON bytes per UTF-16 code unit plus two quotes: + // 174762 code units fit; one more exceeds the 1 MiB bound. + for (const length of [174_762, 174_763]) { + const text = "x".repeat(length); + const expression = + syntax === "quoted" ? JSON.stringify(text) : `\`${text}\``; + const ir = analyzeJavaScriptSemantics(`const answer = ${expression};`); + const value = topLevelBinding(ir, "answer").value; + if (length === 174_762) { + expect(value.status).toBe("literal"); + if (value.status !== "literal") + throw new Error("Expected exact value"); + expect(typeof value.value === "string" && value.value.length).toBe( + length, + ); + expect(ir.coverage.resourceLimits ?? []).toEqual([]); + } else { + expect(value.status).toBe("unknown"); + expect(value).toMatchObject({ resourceLimit: "primitive-bytes" }); + expect(ir.coverage).toMatchObject({ + status: "partial", + omittedCount: null, + resourceLimits: ["primitive-bytes"], + }); + } + } + }, + ); + it("returns an explicit unknown before expanding a large addition product", () => { const term = '(choice ? "a" : "b")'; const expression = Array.from({ length: 20 }, () => term).join(" + "); @@ -256,7 +369,7 @@ describe("semantic primitive allocation and binding reuse", () => { expect(ir.coverage).toMatchObject({ status: "partial", omittedCount: null, - resourceLimits: ["primitive-bytes"], + resourceLimits: expect.arrayContaining(["primitive-bytes"]), }); }); diff --git a/src/domain/javascript/javascriptSemanticDataEffectHelpers.ts b/src/domain/javascript/javascriptSemanticDataEffectHelpers.ts index 5c3ed9ddd4..fcc1d0b99d 100644 --- a/src/domain/javascript/javascriptSemanticDataEffectHelpers.ts +++ b/src/domain/javascript/javascriptSemanticDataEffectHelpers.ts @@ -915,15 +915,6 @@ export const dataEffectArgumentBindingId = ( ? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null) : null; -/** Read one JSON primitive default without evaluating code. */ -export const dataEffectPrimitive = ( - node: t.Node, -): string | number | boolean | null => { - if (t.isStringLiteral(node) || t.isNumericLiteral(node)) return node.value; - if (t.isBooleanLiteral(node)) return node.value; - return null; -}; - /** * Read one static string or expression-free template. A template recovered * with an invalid escape has no cooked value; its raw spelling is not a diff --git a/src/domain/javascript/javascriptSemanticDataEffects.ts b/src/domain/javascript/javascriptSemanticDataEffects.ts index 6d1de8325e..e3d670cf56 100644 --- a/src/domain/javascript/javascriptSemanticDataEffects.ts +++ b/src/domain/javascript/javascriptSemanticDataEffects.ts @@ -8,11 +8,13 @@ import type { } from "./javascriptSemanticIr.js"; import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js"; import { + readExactJavaScriptLiteral, semanticStaticPropertyKey, semanticStaticPropertyName, unwrapJavaScriptExpression, } from "./javascriptAstValues.js"; import { + recordSemanticValueLimit, resolveSemanticBindingState, semanticResolutionBlocked, type JavaScriptSemanticAnalysisState, @@ -22,7 +24,6 @@ import { dataEffectArgumentBindingId as argumentBindingId, dataEffectLiteralString as literalString, dataEffectMemberCallee as memberCallee, - dataEffectPrimitive as primitiveValue, isSemanticProcessObject as isProcessObject, outerDataEffectBinding as outerAssignedBinding, assignedSemanticResultBindings, @@ -31,6 +32,8 @@ import { type DataEffectTraversalContext as TraversalContext, } from "./javascriptSemanticDataEffectHelpers.js"; import { range } from "./javascriptStaticAnalysisHelpers.js"; +import { semanticPrimitiveSet } from "./javascriptSemanticPrimitives.js"; +import type { JavaScriptSemanticValue } from "./javascriptSemanticValueTypes.js"; interface DataEffectAnalysis { readonly configurationOperations: readonly JavaScriptSemanticConfigurationOperation[]; @@ -175,6 +178,11 @@ const collectDefaults = ( const source = unwrapJavaScriptExpression(node.left).node; const candidate = configurationsByNode.get(source); if (candidate === undefined) return; + const literal = readExactJavaScriptLiteral(node.right); + const value: JavaScriptSemanticValue = literal.found + ? semanticPrimitiveSet([literal.value]) + : { status: "unknown", reason: "Default is not an exact primitive." }; + recordSemanticValueLimit(context.state, value); output.push({ configId: `config:default:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: "default", @@ -183,8 +191,11 @@ const collectDefaults = ( resultBindingId: outerAssignedBinding(node, context), key: candidate.operation.key, sourceConfigId: candidate.operation.configId, - value: primitiveValue(node.right), - resolution: primitiveValue(node.right) === null ? "partial" : "complete", + value: value.status === "literal" ? value.value : null, + ...(value.status === "unknown" && value.resourceLimit !== undefined + ? { valueResourceLimit: value.resourceLimit } + : {}), + resolution: value.status === "literal" ? "complete" : "partial", }); }); return output; diff --git a/src/domain/javascript/javascriptSemanticIr.ts b/src/domain/javascript/javascriptSemanticIr.ts index d8349e895d..5355b3ad33 100644 --- a/src/domain/javascript/javascriptSemanticIr.ts +++ b/src/domain/javascript/javascriptSemanticIr.ts @@ -1,5 +1,8 @@ import type { JavaScriptSourceRange } from "./javascriptStaticAnalysisTypes.js"; -import type { JavaScriptSemanticValue } from "./javascriptSemanticValueTypes.js"; +import type { + JavaScriptSemanticResourceLimit, + JavaScriptSemanticValue, +} from "./javascriptSemanticValueTypes.js"; import type { JavaScriptSemanticCoverage, JavaScriptSemanticReturnCoverage, @@ -302,6 +305,7 @@ export interface JavaScriptSemanticConfigurationOperation { readonly key: string | null; readonly sourceConfigId: string | null; readonly value: string | number | boolean | null; + readonly valueResourceLimit?: JavaScriptSemanticResourceLimit; readonly resolution: "complete" | "partial"; } diff --git a/src/domain/javascript/javascriptSemanticPrimitives.ts b/src/domain/javascript/javascriptSemanticPrimitives.ts index abb1f015c3..b66451cddb 100644 --- a/src/domain/javascript/javascriptSemanticPrimitives.ts +++ b/src/domain/javascript/javascriptSemanticPrimitives.ts @@ -14,7 +14,7 @@ import { export const semanticPrimitiveSet = ( values: readonly JavaScriptSemanticPrimitive[], ): JavaScriptSemanticValue => { - const uniqueValues = new Set(); + let uniqueValues: Set | undefined; for (const value of values) { if (typeof value === "number" && !Number.isFinite(value)) return { @@ -26,10 +26,19 @@ export const semanticPrimitiveSet = ( status: "unknown", reason: "Negative zero cannot be preserved by JSON primitive evidence.", }; + // The common literal path needs validation, but no deduplication, sorting + // or canonical-key allocation. Check strings before retaining the value. + if (values.length === 1) + return typeof value === "string" && + exceedsSemanticPrimitiveStringByteBudget([value]) + ? semanticResourceLimitUnknown("primitive-bytes") + : { status: "literal", value }; + uniqueValues ??= new Set(); uniqueValues.add(value); if (uniqueValues.size > SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT) return semanticResourceLimitUnknown("primitive-candidates"); } + if (uniqueValues === undefined) return { status: "union", values: [] }; if ( exceedsSemanticPrimitiveStringByteBudget( [...uniqueValues].flatMap((value) => diff --git a/src/domain/javascript/javascriptSemanticResourceLimits.ts b/src/domain/javascript/javascriptSemanticResourceLimits.ts index 5e2ae5a960..8938a6e249 100644 --- a/src/domain/javascript/javascriptSemanticResourceLimits.ts +++ b/src/domain/javascript/javascriptSemanticResourceLimits.ts @@ -77,25 +77,6 @@ export const semanticResourceLimitUnknown = ( resourceLimit, }); -/** Collect resource-limit classifications from nested semantic values. */ -export const semanticResourceLimitsIn = ( - values: readonly JavaScriptSemanticValue[], -): readonly JavaScriptSemanticResourceLimit[] => { - const found = new Set(); - const pending = [...values]; - while (pending.length > 0) { - const value = pending.pop(); - if (value === undefined) continue; - if (value.status === "unknown" && value.resourceLimit !== undefined) - found.add(value.resourceLimit); - else if (value.status === "object") - for (const property of value.properties) pending.push(property.value); - else if (value.status === "array") - for (const item of value.items) pending.push(item.value); - } - return [...found].sort(); -}; - /** Display label for one stable resource classification. */ export const semanticResourceLimitReason = ( resourceLimit: JavaScriptSemanticResourceLimit, diff --git a/src/domain/javascript/javascriptSemanticState.ts b/src/domain/javascript/javascriptSemanticState.ts index 6f26f68b44..bd5f88f9ec 100644 --- a/src/domain/javascript/javascriptSemanticState.ts +++ b/src/domain/javascript/javascriptSemanticState.ts @@ -14,6 +14,10 @@ import type { JavaScriptSemanticReference, } from "./javascriptSemanticIr.js"; import type { JavaScriptModuleResolutionMode } from "./javascriptModuleResolutionMode.js"; +import type { + JavaScriptSemanticResourceLimit, + JavaScriptSemanticValue, +} from "./javascriptSemanticValueTypes.js"; /** Internal initializer plus a destructuring/member projection. */ interface JavaScriptSemanticInitializer { @@ -75,6 +79,8 @@ export interface JavaScriptSemanticAnalysisState { readonly program: t.Program; /** Effective loader for static imports in this source artifact. */ readonly staticImportResolutionMode: JavaScriptModuleResolutionMode; + /** Bounds encountered during this analysis, independent of later projections. */ + readonly valueResourceLimits: Set; readonly parentsByNode: WeakMap; readonly scopes: JavaScriptSemanticScopeState[]; readonly scopesById: Map; @@ -110,6 +116,15 @@ export interface JavaScriptSemanticAnalysisState { readonly ambientWriteEffects: JavaScriptSemanticMemberEffect[]; } +/** Record one producer outcome without traversing or retaining its value graph. */ +export const recordSemanticValueLimit = ( + state: JavaScriptSemanticAnalysisState, + value: JavaScriptSemanticValue, +): void => { + if (value.status === "unknown" && value.resourceLimit !== undefined) + state.valueResourceLimits.add(value.resourceLimit); +}; + /** One parsed source owns its optional indexes and their per-source lifetime. */ export interface JavaScriptBindingIndex { readonly sourceType: t.Program["sourceType"]; diff --git a/src/domain/javascript/javascriptSemanticValues.ts b/src/domain/javascript/javascriptSemanticValues.ts index 2c4addefd6..0b30984c79 100644 --- a/src/domain/javascript/javascriptSemanticValues.ts +++ b/src/domain/javascript/javascriptSemanticValues.ts @@ -26,6 +26,7 @@ import type { JavaScriptSemanticValue, } from "./javascriptSemanticValueTypes.js"; import { + recordSemanticValueLimit, resolveSemanticBindingState, type JavaScriptSemanticAnalysisState, type JavaScriptSemanticBindingState, @@ -451,6 +452,15 @@ const evaluateBinding = ( const evaluateExpression = ( node: t.Node, context: EvaluationContext, +): JavaScriptSemanticValue => { + const value = evaluateExpressionValue(node, context); + recordSemanticValueLimit(context.state, value); + return value; +}; + +const evaluateExpressionValue = ( + node: t.Node, + context: EvaluationContext, ): JavaScriptSemanticValue => { if (context.expressionDepth > SEMANTIC_EXPRESSION_DEPTH_LIMIT) { context.dependence.lowest = EVERY_CALLER; @@ -471,10 +481,7 @@ const evaluateExpression = ( }); } const literal = readExactJavaScriptLiteral(node); - if (literal.found) - return typeof literal.value === "number" - ? primitiveSet([literal.value]) - : { status: "literal", value: literal.value }; + if (literal.found) return primitiveSet([literal.value]); if (t.isIdentifier(node)) { const binding = resolveSemanticBindingState(context.state, node, node.name); return binding === undefined @@ -757,11 +764,12 @@ const projectValue = ( value: JavaScriptSemanticValue, projection: readonly (string | number | null)[], ): JavaScriptSemanticValue => { + if (isSemanticResourceLimit(value)) return value; if (projection.includes(null)) return { status: "unknown", reason: "Cannot project a dynamic property." }; const path = projection.map((key) => String(key)); const slot = semanticSlotAtPath(value, path); - return slot.presence === "present" + return slot.presence === "present" || isSemanticResourceLimit(slot.value) ? slot.value : { status: "unknown", diff --git a/tests/acceptance/applications/javascriptEndpointPayload.test.ts b/tests/acceptance/applications/javascriptEndpointPayload.test.ts index 75b54e26e4..e43d146687 100644 --- a/tests/acceptance/applications/javascriptEndpointPayload.test.ts +++ b/tests/acceptance/applications/javascriptEndpointPayload.test.ts @@ -30,9 +30,12 @@ cliTest( surrogate, replacement, ]; - const source = values + const requests = values .map((value) => `fetch(${JSON.stringify(value)});`) .join("\n"); + // The primitive lattice is bounded; exact endpoint lookup identities stay + // available for feature/semantic tracing and version comparison. + const source = `${requests}\nexport const oversized = ${JSON.stringify(endpoint)};`; const sourcePath = join(root, "main.js"); await writeFile(sourcePath, source); const cliResponse = await cli.run({ @@ -79,6 +82,17 @@ cliTest( const result = javascriptApplicationAnalysisResultSchema.parse( evidence.normalized_result, ); + expect(result.graph.coverage.limits).toContainEqual( + expect.objectContaining({ + name: "javascript_semantic_primitive_bytes", + }), + ); + expect( + result.semantic_graph.nodes.some( + ({ kind, properties }) => + kind === "literal" && properties.value === endpoint, + ), + ).toBe(false); const endpoints = result.graph.nodes.filter( (node) => node.kind === "endpoint", ); @@ -121,18 +135,18 @@ cliTest( available: true, value: { kind: "source-range", source: "main.js" }, }); - const requests = result.semantic_graph.nodes.filter( + const requestNodes = result.semantic_graph.nodes.filter( (node) => node.kind === "request" && node.properties.endpoint === endpoint, ); - expect(requests).toHaveLength(1); + expect(requestNodes).toHaveLength(1); await verifyEndpointWorkflows({ client, evidenceId: evidence.evidence_id, endpoint, endpointNodeId: endpointNode.node_id, - requestNodeIds: requests.map(({ node_id }) => node_id), + requestNodeIds: requestNodes.map(({ node_id }) => node_id), root, source, sourcePath, diff --git a/tests/boundary/mcp/javascriptLiteralBudget.test.ts b/tests/boundary/mcp/javascriptLiteralBudget.test.ts new file mode 100644 index 0000000000..0a396bf233 --- /dev/null +++ b/tests/boundary/mcp/javascriptLiteralBudget.test.ts @@ -0,0 +1,137 @@ +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { createHash } from "node:crypto"; +import { readFile, 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 { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { cliTest } from "../../support/cli/cliFixture.js"; + +const client = new Client({ name: "literal-budget-boundary", 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 large = "x".repeat(200_000); +for (const scenario of [ + { name: "quoted literal", expression: JSON.stringify(large), limited: true }, + { name: "cooked template", expression: `\`${large}\``, limited: true }, + { + name: "limited member receiver", + expression: `(${JSON.stringify(large)}).field`, + limited: true, + }, + { + name: "configuration-only default", + expression: `process.env.MODE ?? ${JSON.stringify(large)}`, + bare: true, + limited: true, + }, + { + name: "equivalent addition", + expression: `${JSON.stringify(large.slice(0, 100_000))} + ${JSON.stringify(large.slice(100_000))}`, + limited: true, + }, + { name: "small exact control", expression: '"small"', limited: false }, +]) { + cliTest( + `preserves normalized string-byte coverage for ${scenario.name}`, + async ({ cli }) => { + const root = await createTestTempDirectory("rea-literal-budget-"); + const path = join(root, "main.js"); + const source = + "bare" in scenario + ? `${scenario.expression};\n` + : `export const payload = ${scenario.expression};\n`; + await writeFile(path, source); + const digest = createHash("sha256").update(source).digest("hex"); + const cliResponse = await cli.run({ + arguments: ["analyze-javascript-application", root, "--json"], + timeoutMs: 60_000, + }); + expect(cliResponse.exitCode).toBe(0); + const cliEvidence = toolContract( + "analyze_javascript_application", + ).outputSchema.parse(cliResponse.json); + const response = await client.callTool({ + name: "analyze_javascript_application", + arguments: { input_path: root }, + }); + expect(response.isError).not.toBe(true); + const evidence = toolContract( + "analyze_javascript_application", + ).outputSchema.parse(response.structuredContent); + expect(evidence.evidence_id).toBe(cliEvidence.evidence_id); + + for (const candidate of [cliEvidence, evidence]) { + const analysis = javascriptApplicationAnalysisResultSchema.parse( + candidate.normalized_result, + ); + const stringLengths = analysis.semantic_graph.nodes.flatMap( + ({ properties }) => + typeof properties.value === "string" + ? [properties.value.length] + : [], + ); + expect(stringLengths).not.toContain(large.length); + if (scenario.limited) { + expect(analysis.graph.coverage.status).toBe("partial"); + expect(analysis.graph.coverage.limits).toContainEqual( + expect.objectContaining({ + name: "javascript_semantic_primitive_bytes", + unit: "bytes", + value: 1_048_576, + }), + ); + expect(analysis.semantic_graph.unknowns).toContainEqual( + expect.objectContaining({ + reason: "resource-limit", + detail: expect.stringMatching( + /primitive string-byte budget exceeded/i, + ), + }), + ); + } else { + expect(stringLengths).toContain(5); + expect(analysis.graph.coverage.limits).toEqual([]); + } + if ("bare" in scenario) { + const node = analysis.semantic_graph.nodes.find( + ({ kind, properties }) => + kind === "config-source" && properties.source_kind === "default", + ); + expect(node?.properties).toMatchObject({ + value: null, + value_resource_limit: "primitive-bytes", + }); + expect(analysis.semantic_graph.unknowns).toContainEqual( + expect.objectContaining({ + node_id: node?.node_id, + reason: "resource-limit", + }), + ); + } + expect( + analysis.graph.nodes.find((node) => node.kind === "javascript-asset") + ?.identity, + ).toMatchObject({ sha256: digest }); + } + expect(await readFile(path, "utf8")).toBe(source); + await client.ping(); + }, + ); +} diff --git a/tests/boundary/mcp/javascriptLiteralPayload.test.ts b/tests/boundary/mcp/javascriptLiteralPayload.test.ts index b7fcbb8764..b35cdbd3bd 100644 --- a/tests/boundary/mcp/javascriptLiteralPayload.test.ts +++ b/tests/boundary/mcp/javascriptLiteralPayload.test.ts @@ -11,13 +11,31 @@ import { javaScriptSemanticTraceResultSchema } from "../../../src/domain/javascr import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; +const boundedLargeLiteralPayloads = (): readonly string[] => { + // Preserve the former aggregate payload size while each normalized value + // stays within its independent semantic byte budget. + const values = Array.from({ length: 16 }, (_, index) => + `rea_literal_payload_ğ\\"\0${String(index)}_`.padEnd(24, "x").repeat(4096), + ); + expect( + values.reduce((total, value) => total + value.length, 0), + ).toBeGreaterThanOrEqual(24 * 65_536); + expect(values.every((value) => value.length * 6 + 2 <= 1_048_576)).toBe(true); + return values; +}; + cliTest( - "delivers complete large literals through CLI and default stdio MCP without metadata copies", + "delivers aggregate large literal payloads through CLI and default stdio MCP without metadata copies", async ({ cli }) => { const root = await createTestTempDirectory("rea-literal-payload-"); - const value = 'rea_literal_payload_ğ\\"\0'.padEnd(24, "x").repeat(65_536); + const values = boundedLargeLiteralPayloads(); + const value = values[0]; + if (value === undefined) throw new Error("Missing literal payload"); const source = [ - `export const payload = ${JSON.stringify(value)};`, + ...values.map( + (payload, index) => + `export const payload${String(index)} = ${JSON.stringify(payload)};`, + ), 'export const choice = flag ? 1 : "1";', 'export const empty = "";', ].join("\n"); @@ -52,7 +70,7 @@ cliTest( name: "analyze_javascript_application", arguments: { input_path: root, format: "directory" }, }); - // The old payload-bearing role key/label exceed the default wire budget. + // Default wire delivery must retain every admitted value in the aggregate. expect(response.isError).not.toBe(true); const evidence = toolContract( "analyze_javascript_application", @@ -67,6 +85,15 @@ cliTest( const graph = javascriptApplicationAnalysisResultSchema.parse( evidence.normalized_result, ).semantic_graph; + const retainedPayloads = graph.nodes + .filter( + (node) => + node.kind === "literal" && + typeof node.properties.value === "string" && + values.includes(node.properties.value), + ) + .map((node) => node.properties.value); + expect(retainedPayloads.toSorted()).toEqual(values.toSorted()); const literal = graph.nodes.find( (node) => node.kind === "literal" && node.properties.value === value, );