diff --git a/docs/javascript-application-workflows.md b/docs/javascript-application-workflows.md index 43a8001637..ae109803fb 100644 --- a/docs/javascript-application-workflows.md +++ b/docs/javascript-application-workflows.md @@ -121,9 +121,10 @@ match. An export is linked to a callable only through exact lexical/module relationships recovered by the AST analysis. Direct return expressions, including expression-bodied arrows, are evaluated -through a bounded execution-free value lattice. Literal object fields are -static observations. Calls, dynamic spreads, computed keys, parser recovery, -and exhausted value, property, or return-site limits remain partial or unknown. +through an execution-free value lattice. Literal object fields and direct +return sites are retained without count quotas. Calls, dynamic spreads, +computed keys, parser recovery, and the value-recursion depth guard remain +partial or unknown. Nested callable returns are not assigned to their parent callable. Projected graph observations carry source ranges but never source text. @@ -176,3 +177,9 @@ Replay observations retain `controlled-replay` authority. They cannot promote static inference into passive runtime observation or prove that the original application, renderer, preload, main process, or remote service behaved identically. + +Module and explicit case lists have no fixed item-count ceiling. Planning checks +the complete request against the committed worker-protocol byte limit before +probing executables or reading modules; aggregate module-source and case-input +bytes have their own limits. Preset-generated cases remain capped at 64 because +REA expands that scalar request into case records before sandbox startup. diff --git a/docs/javascript-runtime-reconciliation.md b/docs/javascript-runtime-reconciliation.md index 03804600a3..8ee62aa5a0 100644 --- a/docs/javascript-runtime-reconciliation.md +++ b/docs/javascript-runtime-reconciliation.md @@ -11,7 +11,7 @@ The operation performs no filesystem, browser, or Electron I/O. It accepts only semantically verified Evidence produced by `analyze_javascript_application`, `inspect_web_page`, `inspect_electron_page`, or `observe_javascript_runtime`; matching operation names alone -is insufficient. Provider identity, predicate version, subject digest, target +is insufficient. Provider identity, predicate type, subject digest, target parameters, and normalized-result schemas are checked before reconciliation. ## Workflow diff --git a/docs/managed-code-analysis.md b/docs/managed-code-analysis.md index 88b0b5ea5c..0599bfb060 100644 --- a/docs/managed-code-analysis.md +++ b/docs/managed-code-analysis.md @@ -106,23 +106,22 @@ EncBaseId because ECMA-335 metadata does not require those bit patterns. - exact raw bytes or bounded value digest; - raw CIL digest plus separately reported method-header, locals-token, and exception-region observations where available; -- a deterministic v1 decoded-instruction-tuple digest for completely decoded +- a deterministic decoded-instruction-tuple digest for completely decoded CIL, with the limitations below. -### Shipped decoded-CIL fingerprint v1 +### Shipped decoded-CIL fingerprint `inspect_managed_members` exposes two method CIL hashes with different byte boundaries: -| Field | Shipped v1 input | Excluded from the digest | +| Field | Input | Excluded from the digest | | ---------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `il_sha256` | The exact `il_size` raw CIL bytes after the tiny or fat method header | Method header, locals signature blob, alignment, and extra sections including exception clauses | | `normalized_il_sha256` | UTF-8 `JSON.stringify` output for the instruction-order array of `[opcode, operand_kind, operand]` tuples | Instruction offsets except projected scalar branch targets, switch targets, resolved metadata/string identities, method header, locals, and exception regions | The normalized field is available only when the entire CIL stream decodes with status `present`. A partial, malformed, absent, or over-limit body reports -`null`. The tuple format is defined here and does not add a schema-version field -to the member result. +`null`. The tuple format is defined here. Tuple operands use these exact projections: @@ -134,17 +133,17 @@ Tuple operands use these exact projections: - signed integer and variable operands: decimal strings; floating-point operands: JavaScript's `String` representation of the decoded IEEE value. -Opcode names retain short/long forms and prefix opcodes. This makes v1 a +Opcode names retain short/long forms and prefix opcodes. This makes the value a reproducible decoded-tuple fingerprint, not a canonical semantic CIL identity: it does not resolve tokens across MVIDs, does not commit full control flow or exception semantics, and does not replace the raw CIL digest. Cross-build comparison uses the value only as one unique-only structural tier and reports -ambiguity when that evidence is insufficient. A future normalized-CIL v2 must -have an explicit algorithm version, canonical token/literal identities, +ambiguity when that evidence is insufficient. A future normalized-CIL +commitment would need canonical token/literal identities, complete branch and switch targets, and explicit locals/exception semantics. For example, a decoded `nop; ret` body serializes exactly as -`[["nop","none",null],["ret","none",null]]` and has v1 digest +`[["nop","none",null],["ret","none",null]]` and has digest `5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636`. This vector specifies the tuple order, `null` handling, absence of whitespace, UTF-8 encoding, and lowercase hexadecimal output. @@ -205,7 +204,7 @@ bounded set of observations needed to investigate a proposition: - exact strings, numeric constants, enum-like values, and serialization keys; - branch, switch, exception, loop, call, and field-flow shape; - construction and generic-instantiation sites; -- raw CIL and decoded-tuple-v1 commitments, plus separate header, locals, and +- raw CIL and decoded-tuple commitments, plus separate header, locals, and exception observations; - callers, callees, shared fields, and competing candidates; and - limitations from reflection, dynamic dispatch, generated code, protection, @@ -218,12 +217,11 @@ turn that role into the method's durable identity. Cross-version matching is two-stage: 1. Exact identity requires compatible artifact/module commitments and exact - raw CIL identity; v1 does not expose a standalone complete-method-body - digest. -2. Structural identity compares normalized signatures, the limited v1 + raw CIL identity; no standalone complete-method-body digest is exposed. +2. Structural identity compares normalized signatures, the limited decoded-CIL tuple fingerprint, constants, and bounded shape context. It reports all candidates at the winning score and remains ambiguous when the - evidence does not distinguish them. The v1 digest does not itself remap + evidence does not distinguish them. The digest does not itself remap metadata tokens. Tokens are always remapped through observed structure. A caller cannot carry @@ -374,8 +372,8 @@ manifest supplies those fields. It then pages each declared MethodDef token directly by row, so selected methods do not need to appear in the first member page of a large application. `il_length` remains accepted as a legacy alias for `il_size`; optional `il_sha256` locks the exact raw CIL bytes in addition to -REA's member-result-v1 decoded-instruction-tuple digest. That normalized field -has the v1 limits documented above and is not a complete semantic CIL identity. +REA's decoded-instruction-tuple digest. That normalized field has the limits +documented above and is not a complete semantic CIL identity. The optional `application_graph` block reuses those exact-build method commitments. For each referenced MethodDef token, the verifier builds a bounded @@ -424,7 +422,7 @@ The managed-code track advances as reviewable pull requests: 1. accepted evidence/provider boundary (this document and ADR); 2. read-only artifact triage and exact identity (shipped); 3. bounded metadata, signatures, method bodies, raw CIL hashes, and the limited - decoded-instruction-tuple fingerprint v1 (shipped; normalized-CIL v2 + decoded-instruction-tuple fingerprint (shipped; complete normalized-CIL semantics remain planned); 4. obfuscation-resistant slices and cross-version comparison (shipped for static member observations); diff --git a/scripts/lib/managed-completion-report.mjs b/scripts/lib/managed-completion-report.mjs index 3ad9e66fe0..45b74f5559 100644 --- a/scripts/lib/managed-completion-report.mjs +++ b/scripts/lib/managed-completion-report.mjs @@ -17,7 +17,7 @@ const completionClaim = ({ ...new Set(targets.map(({ sha256: digest }) => digest)), ].sort(); const evidence = createEvidence(targets[0], COMPLETION_PROVIDER, { - predicateType: "rea.verification/v1", + predicateType: "rea.verification", operation: "verify_managed_conformance", parameters: { scenario_id: scenarioId, diff --git a/src/application/AndroidApplicationService.ts b/src/application/AndroidApplicationService.ts index 2c98b433a7..269b65f669 100644 --- a/src/application/AndroidApplicationService.ts +++ b/src/application/AndroidApplicationService.ts @@ -19,7 +19,7 @@ export const projectAndroidApplicationEvidence = ( schema: androidApplicationProjectionInputSchema, project: projectAndroidApplication, operation: OPERATION, - predicateType: "rea.android-application-graph/v1", + predicateType: "rea.android-application-graph", provider: ANDROID_APPLICATION_PROVIDER, subjectFormat: () => "apk", protocolError: "Android application projection produced an invalid result", diff --git a/src/application/AppleApplicationService.ts b/src/application/AppleApplicationService.ts index 29ae9c19be..f90db3c39d 100644 --- a/src/application/AppleApplicationService.ts +++ b/src/application/AppleApplicationService.ts @@ -19,7 +19,7 @@ export const projectAppleApplicationEvidence = ( schema: appleApplicationProjectionInputSchema, project: projectAppleApplication, operation: OPERATION, - predicateType: "rea.apple-application-graph/v1", + predicateType: "rea.apple-application-graph", provider: APPLE_APPLICATION_PROVIDER, subjectFormat: () => "ipa", protocolError: "Apple application projection produced an invalid result", diff --git a/src/application/ApplicationWorkflowEvidenceResolver.ts b/src/application/ApplicationWorkflowEvidenceResolver.ts index e48f84283a..3c4830ceb8 100644 --- a/src/application/ApplicationWorkflowEvidenceResolver.ts +++ b/src/application/ApplicationWorkflowEvidenceResolver.ts @@ -34,11 +34,11 @@ const APPLICATION_GRAPH_IDENTITIES = [ }, { operation: "reconcile_javascript_runtime", - predicate: "rea.javascript-runtime-reconciliation/v1", + predicate: "rea.javascript-runtime-reconciliation", }, { operation: "project_managed_application_graph", - predicate: "rea.managed-application-graph/v1", + predicate: "rea.managed-application-graph", }, ] as const satisfies readonly EvidenceSemanticIdentity[]; diff --git a/src/application/DirectAnalysis.ts b/src/application/DirectAnalysis.ts index d803a8486f..3478237691 100644 --- a/src/application/DirectAnalysis.ts +++ b/src/application/DirectAnalysis.ts @@ -1,4 +1,5 @@ import { projectPermissionFailure } from "./PermissionFailure.js"; +import { artifactExtractionPermissionRequest } from "./ArtifactExtractionDestination.js"; import { parseConfig, type AppConfig } from "../config.js"; import type { JsonValue } from "../domain/jsonValue.js"; import { EnhancedTools } from "./EnhancedTools.js"; @@ -118,23 +119,11 @@ const authorizeAnalysis = async ( const requests = []; if (tool === "extract_artifact" && typeof arguments_.output_root === "string") requests.push({ - capability: "artifact_extract" as const, - path: arguments_.output_root, + request: artifactExtractionPermissionRequest(), access: "write" as const, }); for (const request of requests) { - const result = await authority.authorize( - { - capability: request.capability, - roots: [request.path], - executables: [], - environment_names: [], - network: "none", - mount: false, - operation_identity: `${tool}:${request.capability}:${request.path}`, - }, - request.access, - ); + const result = await authority.authorize(request.request, request.access); if (!result.ok) return err(projectPermissionFailure(result.error)); } if ( diff --git a/src/application/JavaScriptApplicationEvidenceGraph.ts b/src/application/JavaScriptApplicationEvidenceGraph.ts index b6c775af06..ad59e47b04 100644 --- a/src/application/JavaScriptApplicationEvidenceGraph.ts +++ b/src/application/JavaScriptApplicationEvidenceGraph.ts @@ -39,13 +39,13 @@ export const parseApplicationGraphEvidence = ( return staticApplicationSource(evidence); if ( evidence.operation === "reconcile_javascript_runtime" && - evidence.predicate_type === "rea.javascript-runtime-reconciliation/v1" && + evidence.predicate_type === "rea.javascript-runtime-reconciliation" && providerMatches(evidence, JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER) ) return staticRuntimeSource(evidence); if ( evidence.operation === "project_managed_application_graph" && - evidence.predicate_type === "rea.managed-application-graph/v1" && + evidence.predicate_type === "rea.managed-application-graph" && providerMatches(evidence, MANAGED_WORKFLOW_PROVIDER) ) return managedApplicationSource(evidence); diff --git a/src/application/JavaScriptApplicationWorkflowEvidence.ts b/src/application/JavaScriptApplicationWorkflowEvidence.ts index a87b0a0f07..671ca1caf0 100644 --- a/src/application/JavaScriptApplicationWorkflowEvidence.ts +++ b/src/application/JavaScriptApplicationWorkflowEvidence.ts @@ -17,7 +17,7 @@ export const createApplicationFeatureTraceEvidence = ( result: ApplicationFeatureTraceResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.application-feature-trace/v1", + predicateType: "rea.application-feature-trace", operation: "trace_application_feature", parameters, result: jsonValueSchema.parse(result), @@ -35,7 +35,7 @@ export const createJavaScriptSemanticTraceEvidence = ( result: JavaScriptSemanticTraceResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.javascript-semantic-trace/v1", + predicateType: "rea.javascript-semantic-trace", operation: "trace_javascript_semantics", parameters, result: jsonValueSchema.parse(result), @@ -53,7 +53,7 @@ export const createApplicationVersionComparisonEvidence = ( result: ApplicationVersionComparisonResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.application-version-comparison/v1", + predicateType: "rea.application-version-comparison", operation: "compare_application_versions", parameters, result: jsonValueSchema.parse(result), @@ -71,7 +71,7 @@ export const createSourceToBundleComparisonEvidence = ( result: SourceToBundleComparisonResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.source-to-bundle-comparison/v1", + predicateType: "rea.source-to-bundle-comparison", operation: "compare_source_to_bundle", parameters, result: jsonValueSchema.parse(result), @@ -89,7 +89,7 @@ export const createJavaScriptExportShapeComparisonEvidence = ( result: JavaScriptExportShapeComparisonResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.javascript-export-shape-comparison/v1", + predicateType: "rea.javascript-export-shape-comparison", operation: "compare_javascript_export_shapes", parameters, result: jsonValueSchema.parse(result), diff --git a/src/application/JavaScriptArtifactAnalysis.ts b/src/application/JavaScriptArtifactAnalysis.ts index 1653791290..720996ccf7 100644 --- a/src/application/JavaScriptArtifactAnalysis.ts +++ b/src/application/JavaScriptArtifactAnalysis.ts @@ -124,21 +124,13 @@ const analyzeArtifactFile = ( parsed, { maxAstNodes: remainingNodes, - maxFindings: Number.MAX_SAFE_INTEGER, - maxModules: Number.MAX_SAFE_INTEGER, deadline: context.deadline, now: context.now, }, ); const staticFindings = findingCount(analysis); - const semanticLimits: JavaScriptSemanticLimits = { - ...DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS, - maxScopes: remainingNodes, - maxBindings: remainingNodes, - maxCallables: remainingNodes, - maxReferences: remainingNodes, - maxModuleLinks: remainingNodes, - }; + const semanticLimits: JavaScriptSemanticLimits = + DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS; const semantics = analysis.parse_status === "complete" || analysis.parse_status === "partial" ? analyzeParsedJavaScriptSemantics(parsed, semanticLimits) @@ -212,13 +204,13 @@ const parsePackage = ( path: file.path, sha256: file.sha256, status: "included", - name: boundedString(value.name), - version: boundedString(value.version), - main: boundedString(value.main), + name: optionalString(value.name), + version: optionalString(value.version), + main: optionalString(value.main), renderer: - boundedString(value.renderer) ?? - boundedString(value.browser) ?? - boundedString(value.module), + optionalString(value.renderer) ?? + optionalString(value.browser) ?? + optionalString(value.module), limitation: null, }; }; @@ -253,7 +245,7 @@ const parseHtmlScripts = ( const baseHref = htmlBaseHref(text); const pattern = /]*\bsrc\s*=\s*(["'])([^"']+)\1[^>]*>/giu; for (const match of text.matchAll(pattern)) { - const script = match[2]?.slice(0, 4_096); + const script = match[2]; if (script === undefined) continue; const start = match.index; scripts.push({ @@ -268,7 +260,7 @@ const parseHtmlScripts = ( const htmlBaseHref = (text: string): string | null => { const match = /]*\bhref\s*=\s*(["'])([^"']+)\1[^>]*>/iu.exec(text); - return match?.[2]?.slice(0, 4_096) ?? null; + return match?.[2] ?? null; }; const parseSourceMap = ( @@ -297,7 +289,7 @@ const parseSourceMap = ( null, "Parse deadline elapsed during source-map decoding.", ); - const maps = flattenSourceMaps(value, 10_000); + const maps = flattenSourceMaps(value); if (maps === undefined) return invalidSourceMap(file, "Source map is not a bounded version 3 map."); return collectSourceMapOriginals(file, maps, context); @@ -372,7 +364,6 @@ const unavailableSourceMap = ( const flattenSourceMaps = ( root: unknown, - maximum: number, ): Readonly>[] | undefined => { if (!isRecord(root) || root.version !== 3) return undefined; const maps: Readonly>[] = []; @@ -381,8 +372,6 @@ const flattenSourceMaps = ( const map = pending.pop(); if (map === undefined || map.version !== 3) return undefined; if (Array.isArray(map.sections)) { - if (pending.length + maps.length + map.sections.length > maximum) - return undefined; for (const section of map.sections) { if (!isRecord(section) || !isRecord(section.map)) return undefined; pending.push(section.map); @@ -454,8 +443,8 @@ const resolveSourceName = (root: string, source: string): string => 4_096, ); -const boundedString = (value: unknown): string | null => - typeof value === "string" && value.length > 0 ? value.slice(0, 4_096) : null; +const optionalString = (value: unknown): string | null => + typeof value === "string" && value.length > 0 ? value : null; const sha256 = (value: string): string => createHash("sha256").update(value).digest("hex"); diff --git a/src/application/JavaScriptArtifactGraphBundlers.ts b/src/application/JavaScriptArtifactGraphBundlers.ts index 85a3baff7f..04c83125e6 100644 --- a/src/application/JavaScriptArtifactGraphBundlers.ts +++ b/src/application/JavaScriptArtifactGraphBundlers.ts @@ -81,15 +81,12 @@ const createBundlerChunkNode = ( bundler: registration.bundler, runtime: registration.runtime, chunk_keys: registration.chunk_keys, - omitted_chunk_keys: registration.omitted_chunk_keys, unknown_chunk_keys: registration.unknown_chunk_keys, runtime_require_name: registration.runtime_require_name, runtime_module_cache_status: registration.runtime_module_cache_status, entry_module_keys: registration.entry_module_keys, - omitted_entry_module_keys: registration.omitted_entry_module_keys, unknown_entry_module_keys: registration.unknown_entry_module_keys, async_chunk_keys: registration.async_chunk_keys, - omitted_async_chunk_keys: registration.omitted_async_chunk_keys, unknown_async_chunk_keys: registration.unknown_async_chunk_keys, module_count: registration.modules.length, }, @@ -140,7 +137,6 @@ const addBundlerModuleNodes = ( structural_fingerprint_status: moduleValue.structural_fingerprint_status, exports: moduleValue.exports, - exports_truncated: moduleValue.exports_truncated, }, evidence: astObservationEvidence({ sha256: file.sha256, diff --git a/src/application/JavaScriptArtifactPathResolution.test.ts b/src/application/JavaScriptArtifactPathResolution.test.ts index c8c1a947cb..e71b09f2d2 100644 --- a/src/application/JavaScriptArtifactPathResolution.test.ts +++ b/src/application/JavaScriptArtifactPathResolution.test.ts @@ -250,8 +250,6 @@ describe("contextual JavaScript module identity", () => { `, { maxAstNodes: 10_000, - maxFindings: 100, - maxModules: 100, deadline: Number.POSITIVE_INFINITY, now: () => 0, }, diff --git a/src/application/JavaScriptReplayPlanning.ts b/src/application/JavaScriptReplayPlanning.ts index fe6896cc73..738e07df07 100644 --- a/src/application/JavaScriptReplayPlanning.ts +++ b/src/application/JavaScriptReplayPlanning.ts @@ -84,12 +84,23 @@ export interface JavaScriptReplayRunner { ) => Promise; } +const assertReplayManifestWithinProtocolLimit = ( + input: ControlledReplayInput, +): void => { + if ( + new TextEncoder().encode(JSON.stringify(input)).byteLength > + input.limits.protocol_bytes + ) + throw new RangeError("Replay plan input exceeds its protocol byte limit"); +}; + /** Read, normalize, and content-address every input to one replay experiment. */ export const prepareReplayPlan = async ( input: ControlledReplayInput, policy: EnabledJavaScriptReplayPolicy, host: JavaScriptReplayHost, ): Promise => { + assertReplayManifestWithinProtocolLimit(input); const cases = replayCases(input).map((item) => ({ ...item, sha256: digestJson(item.arguments), diff --git a/src/application/JavaScriptReturnShapeProjection.ts b/src/application/JavaScriptReturnShapeProjection.ts index da4e6b5d0c..484bcbf436 100644 --- a/src/application/JavaScriptReturnShapeProjection.ts +++ b/src/application/JavaScriptReturnShapeProjection.ts @@ -47,7 +47,7 @@ export const projectJavaScriptExportReturnShapes = (input: { semanticOmitted === null ? null : semanticOmitted + projectionOmitted; const coverage = complete ? input.baseCoverage - : callable.returnCoverage.status === "truncated" || projectionOmitted > 0 + : projectionOmitted > 0 ? truncatedApplicationCoverage(limits, omitted) : partialApplicationCoverage(limits, omitted); const limitations = [ diff --git a/src/application/JavaScriptRuntimeObservationEvidence.ts b/src/application/JavaScriptRuntimeObservationEvidence.ts index fb29842ce3..81b35ac984 100644 --- a/src/application/JavaScriptRuntimeObservationEvidence.ts +++ b/src/application/JavaScriptRuntimeObservationEvidence.ts @@ -26,8 +26,8 @@ export const createJavaScriptRuntimeObservationEvidence = ( createEvidence(undefined, provider, { predicateType: operation === "list_javascript_runtime_targets" - ? "rea.javascript-runtime-target-list/v1" - : "rea.javascript-runtime-observation/v1", + ? "rea.javascript-runtime-target-list" + : "rea.javascript-runtime-observation", operation, parameters: parameters(input), result: jsonValueSchema.parse(result), diff --git a/src/application/JavaScriptRuntimeReconciliationEvidence.ts b/src/application/JavaScriptRuntimeReconciliationEvidence.ts index 1dc8a6529a..c23609c753 100644 --- a/src/application/JavaScriptRuntimeReconciliationEvidence.ts +++ b/src/application/JavaScriptRuntimeReconciliationEvidence.ts @@ -16,7 +16,7 @@ export const createJavaScriptRuntimeReconciliationEvidence = ( result: JavaScriptRuntimeReconciliationResult, ): Evidence => createEvidence(undefined, JAVASCRIPT_RUNTIME_RECONCILIATION_PROVIDER, { - predicateType: "rea.javascript-runtime-reconciliation/v1", + predicateType: "rea.javascript-runtime-reconciliation", operation: "reconcile_javascript_runtime", parameters: parameters(input), result: jsonValueSchema.parse(result), diff --git a/src/application/JavaScriptSemanticGraphBuilder.test.ts b/src/application/JavaScriptSemanticGraphBuilder.test.ts index d32920dfb2..6d143d2d4b 100644 --- a/src/application/JavaScriptSemanticGraphBuilder.test.ts +++ b/src/application/JavaScriptSemanticGraphBuilder.test.ts @@ -173,10 +173,10 @@ it("connects caller results, direct returns, parameters, and captures", () => { expect(capture?.evidence.location).not.toEqual(readOuter.evidence.location); }); -it("preserves semantic analyzer truncation in graph coverage", () => { +it("preserves the semantic value-depth guard in graph coverage", () => { const graph = graphFor( "const first = 1; const second = first; const third = second;", - { maxReferences: 1 }, + { maxValueDepth: 1 }, ); expect(graph.coverage).toMatchObject({ status: "partial", @@ -184,7 +184,7 @@ it("preserves semantic analyzer truncation in graph coverage", () => { omitted_nodes: null, omitted_relations: null, limits: expect.arrayContaining([ - { name: "semantic.maxReferences", value: 1, unit: "items" }, + { name: "semantic.maxValueDepth", value: 1, unit: "depth" }, ]), }); expect( diff --git a/src/application/JavaScriptSemanticGraphBuilder.ts b/src/application/JavaScriptSemanticGraphBuilder.ts index 9db995ff70..41d744848b 100644 --- a/src/application/JavaScriptSemanticGraphBuilder.ts +++ b/src/application/JavaScriptSemanticGraphBuilder.ts @@ -75,7 +75,7 @@ interface FileContext extends SemanticFlowProjectionContext { readonly callResolutions: ReadonlyMap; } -/** Project bounded per-file semantic IR into an artifact-bound companion graph. */ +/** Project per-file semantic IR into an artifact-bound companion graph. */ export const buildJavaScriptSemanticGraph = ({ rootArtifactSha256, applicationGraph, diff --git a/src/application/JavaScriptSemanticGraphFingerprintProjection.ts b/src/application/JavaScriptSemanticGraphFingerprintProjection.ts index 8521806a14..f511373495 100644 --- a/src/application/JavaScriptSemanticGraphFingerprintProjection.ts +++ b/src/application/JavaScriptSemanticGraphFingerprintProjection.ts @@ -19,7 +19,7 @@ export const projectSemanticFunctionFingerprints = ( return [ createJavaScriptSemanticFingerprint({ function_node_id: callable.node_id, - algorithm: "rea.javascript-semantic-function/v1", + algorithm: "rea.javascript-semantic-function", status: fingerprint.status, components: { parameter_arity: fingerprint.components.parameterArity, diff --git a/src/application/JavaScriptSemanticGraphFlowProjection.ts b/src/application/JavaScriptSemanticGraphFlowProjection.ts index 0cb83605bc..6a8c49c020 100644 --- a/src/application/JavaScriptSemanticGraphFlowProjection.ts +++ b/src/application/JavaScriptSemanticGraphFlowProjection.ts @@ -281,13 +281,13 @@ export const projectSemanticFrontiers = ( } }; -/** Publish exact semantic analyzer limits reached by any admitted source file. */ +/** Publish the semantic recursion guard when it caused incomplete recovery. */ export const semanticRecoveryLimits = ( analysis: JavaScriptArtifactAnalysis, ): { readonly name: string; readonly value: number; - readonly unit: "items"; + readonly unit: "depth"; }[] => { const limits = new Map(); for (const { semantic } of analysis.files) { @@ -297,5 +297,5 @@ export const semanticRecoveryLimits = ( } return [...limits] .sort(([left], [right]) => left.localeCompare(right)) - .map(([name, value]) => ({ name, value, unit: "items" })); + .map(([name, value]) => ({ name, value, unit: "depth" })); }; diff --git a/src/application/ManagedApplicationGraphService.ts b/src/application/ManagedApplicationGraphService.ts index 9a63dec38b..97de9a5459 100644 --- a/src/application/ManagedApplicationGraphService.ts +++ b/src/application/ManagedApplicationGraphService.ts @@ -46,7 +46,7 @@ const createManagedApplicationGraphEvidence = ( result: ManagedApplicationGraphResult, ): Evidence => createEvidence(subjectTarget(input), MANAGED_WORKFLOW_PROVIDER, { - predicateType: "rea.managed-application-graph/v1", + predicateType: "rea.managed-application-graph", operation: OPERATION, parameters: { managed_artifact_evidence_id: input.managed_artifact?.evidence_id ?? null, diff --git a/src/application/ManagedMemberComparisonService.ts b/src/application/ManagedMemberComparisonService.ts index 982e7f73d8..1a2d038b16 100644 --- a/src/application/ManagedMemberComparisonService.ts +++ b/src/application/ManagedMemberComparisonService.ts @@ -156,7 +156,7 @@ const createManagedMemberComparisonEvidence = ( result: z.infer, ): Evidence => createEvidence(undefined, MANAGED_WORKFLOW_PROVIDER, { - predicateType: "rea.managed-member-comparison/v1", + predicateType: "rea.managed-member-comparison", operation: "compare_managed_members", parameters: { left_evidence_id: parameters.left.evidence_id, diff --git a/src/application/ManagedNativeVerificationService.ts b/src/application/ManagedNativeVerificationService.ts index 95865bd553..c1b9cd6e01 100644 --- a/src/application/ManagedNativeVerificationService.ts +++ b/src/application/ManagedNativeVerificationService.ts @@ -45,7 +45,7 @@ const createManagedNativeVerificationEvidence = ( result: ManagedNativeVerificationResult, ): Evidence => createEvidence(undefined, MANAGED_WORKFLOW_PROVIDER, { - predicateType: "rea.managed-native-verification/v1", + predicateType: "rea.managed-native-verification", operation: OPERATION, parameters: { managed_boundaries_evidence_id: input.managed_boundaries.evidence_id, diff --git a/src/application/ManagedReconstructionService.ts b/src/application/ManagedReconstructionService.ts index b867450f24..6d6c48eed5 100644 --- a/src/application/ManagedReconstructionService.ts +++ b/src/application/ManagedReconstructionService.ts @@ -59,7 +59,7 @@ const createManagedReconstructionEvidence = ( }, MANAGED_WORKFLOW_PROVIDER, { - predicateType: "rea.managed-reconstruction-import/v1", + predicateType: "rea.managed-reconstruction-import", operation: OPERATION, parameters: { static_members_evidence_id: input.static_members.evidence_id, diff --git a/src/application/ManagedRuntimeCorrelationService.ts b/src/application/ManagedRuntimeCorrelationService.ts index 4bb2f7639b..b8553be931 100644 --- a/src/application/ManagedRuntimeCorrelationService.ts +++ b/src/application/ManagedRuntimeCorrelationService.ts @@ -115,7 +115,7 @@ export const planManagedRuntimeCorrelationEvidenceValidated = async ( }, MANAGED_WORKFLOW_PROVIDER, { - predicateType: "rea.managed-runtime-correlation-plan/v1", + predicateType: "rea.managed-runtime-correlation-plan", operation: OPERATION, parameters: { static_members_evidence_id: staticEvidence.evidence_id, diff --git a/src/application/NodeRuntimeCharacterizationService.ts b/src/application/NodeRuntimeCharacterizationService.ts index 959efe787e..96c903a66c 100644 --- a/src/application/NodeRuntimeCharacterizationService.ts +++ b/src/application/NodeRuntimeCharacterizationService.ts @@ -212,7 +212,7 @@ const createTransformationEvidence = ( }, PROVIDER, { - predicateType: "rea.javascript-export-transformation/v1", + predicateType: "rea.javascript-export-transformation", operation: PREPARE_OPERATION, parameters: { selected_alias: input.selected_alias, @@ -249,7 +249,7 @@ const createCharacterizationEvidence = ( }, PROVIDER, { - predicateType: "rea.runtime-characterization/v1", + predicateType: "rea.runtime-characterization", operation: EXECUTE_OPERATION, parameters: { plan_sha256: prepared.plan.plan_sha256, @@ -340,7 +340,7 @@ const digestPreparation = ( ): string => createHash("sha256") .update( - `rea.node-characterization-preparation/v1\0${instrumentedSha256}\0${replayPlanDigest}`, + `rea.node-characterization-preparation\0${instrumentedSha256}\0${replayPlanDigest}`, ) .digest("hex"); diff --git a/src/application/ReconstructionObligationAuthority.ts b/src/application/ReconstructionObligationAuthority.ts index 1b43d315b8..e85f7f39e3 100644 --- a/src/application/ReconstructionObligationAuthority.ts +++ b/src/application/ReconstructionObligationAuthority.ts @@ -69,7 +69,7 @@ export const evidenceAuthoritySupportsProof = ( if ( evidence.confidence !== "observed" || !proofEvidenceAuthorities[authority].includes(evidence.authority) || - evidence.predicate_type !== "rea.reconstruction-proof/v1" || + evidence.predicate_type !== "rea.reconstruction-proof" || evidence.operation !== "verify_reconstruction_obligations" ) return false; diff --git a/src/application/ReconstructionObligationLedger.authority.test.ts b/src/application/ReconstructionObligationLedger.authority.test.ts index 740481f401..b4a3f84ced 100644 --- a/src/application/ReconstructionObligationLedger.authority.test.ts +++ b/src/application/ReconstructionObligationLedger.authority.test.ts @@ -22,7 +22,7 @@ const proofEvidence = (id: string) => undefined, { id: `fixture-${id}`, name: "Fixture verifier", version: "1" }, { - predicateType: "rea.fixture-verification/v1", + predicateType: "rea.fixture-verification", operation: "run_fixture_verifier", parameters: { id }, result: { passed: true }, @@ -58,7 +58,7 @@ const boundProofEvidence = ( undefined, { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, { - predicateType: "rea.reconstruction-proof/v1", + predicateType: "rea.reconstruction-proof", operation: "verify_reconstruction_obligations", parameters: {}, result: { @@ -248,7 +248,7 @@ describe("reconstruction obligation ledger fail-closed behavior", () => { undefined, { id: "external", name: "External protocol observer", version: "1" }, { - predicateType: "rea.protocol-observation/v1", + predicateType: "rea.protocol-observation", operation: "observe_protocol", parameters: {}, result: { status: 200 }, diff --git a/src/application/ReconstructionObligationLedger.closure.test.ts b/src/application/ReconstructionObligationLedger.closure.test.ts index ce1e07b3c6..ab03008396 100644 --- a/src/application/ReconstructionObligationLedger.closure.test.ts +++ b/src/application/ReconstructionObligationLedger.closure.test.ts @@ -23,7 +23,7 @@ const proofEvidence = (id: string) => undefined, { id: `fixture-${id}`, name: "Fixture verifier", version: "1" }, { - predicateType: "rea.fixture-verification/v1", + predicateType: "rea.fixture-verification", operation: "run_fixture_verifier", parameters: { id }, result: { passed: true }, @@ -59,7 +59,7 @@ const boundProofEvidence = ( undefined, { id: `proof-${id}`, name: "Reconstruction proof", version: "1" }, { - predicateType: "rea.reconstruction-proof/v1", + predicateType: "rea.reconstruction-proof", operation: "verify_reconstruction_obligations", parameters: {}, result: { diff --git a/src/application/ReconstructionObligationLedgerService.ts b/src/application/ReconstructionObligationLedgerService.ts index dee8efe79f..64f667ab64 100644 --- a/src/application/ReconstructionObligationLedgerService.ts +++ b/src/application/ReconstructionObligationLedgerService.ts @@ -64,7 +64,7 @@ const createLedgerEvidence = ( ): Evidence => { const completeLedger = reconstructionObligationLedgerSchema.parse(ledger); return createEvidence(undefined, JAVASCRIPT_APPLICATION_WORKFLOW_PROVIDER, { - predicateType: "rea.reconstruction-obligation-ledger/v1", + predicateType: "rea.reconstruction-obligation-ledger", operation: OPERATION, parameters: jsonObjectSchema.parse({ ledger_id: completeLedger.ledger_id, diff --git a/src/application/UnknownEvidence.ts b/src/application/UnknownEvidence.ts index cbc515e186..5a3ea3b23b 100644 --- a/src/application/UnknownEvidence.ts +++ b/src/application/UnknownEvidence.ts @@ -19,7 +19,7 @@ export const unknownMutationEvidence = ( input: RecordUnknownInput, ): Evidence => createEvidence(target, UNKNOWN_REGISTRY_PROVIDER, { - predicateType: "rea.residual-unknown-mutation/v1", + predicateType: "rea.residual-unknown-mutation", operation: "record_unknown", parameters: { domain: input.domain, severity: input.severity }, result: { diff --git a/src/contracts/javascriptApplicationWorkflowExamples.ts b/src/contracts/javascriptApplicationWorkflowExamples.ts index 8874ed6147..fb9a5b79c2 100644 --- a/src/contracts/javascriptApplicationWorkflowExamples.ts +++ b/src/contracts/javascriptApplicationWorkflowExamples.ts @@ -17,7 +17,7 @@ const reconciliationEvidence = createEvidence( version: "1", }, { - predicateType: "rea.javascript-runtime-reconciliation/v1", + predicateType: "rea.javascript-runtime-reconciliation", operation: "reconcile_javascript_runtime", parameters: { static_layers: diff --git a/src/contracts/javascriptRuntimeReconciliationExample.ts b/src/contracts/javascriptRuntimeReconciliationExample.ts index c028619484..90f25f8ae1 100644 --- a/src/contracts/javascriptRuntimeReconciliationExample.ts +++ b/src/contracts/javascriptRuntimeReconciliationExample.ts @@ -185,7 +185,7 @@ const runtimeEvidence = createEvidence( version: "1", }, { - predicateType: "rea.electron-page-inspection/v1", + predicateType: "rea.electron-page-inspection", operation: "inspect_electron_page", parameters: { cdp_endpoint: "http://127.0.0.1:9223", diff --git a/src/contracts/managedToolContracts.ts b/src/contracts/managedToolContracts.ts index 4fef71b3e7..1cf88ca81c 100644 --- a/src/contracts/managedToolContracts.ts +++ b/src/contracts/managedToolContracts.ts @@ -56,7 +56,7 @@ export const MANAGED_TOOL_CONTRACTS = [ name: "inspect_managed_members", ...toolContractMetadata("inspect_managed_members"), description: - "Inspect PE/CLI metadata members, signatures, raw CIL hashes, member-result-v1 decoded-instruction-tuple hashes, separately reported exception regions, call edges, and field-access anchors without loading or executing target code. Metadata tokens are reported as build-local coordinates bound to the artifact SHA-256 and MVID; the v1 tuple hash does not resolve them or fully commit control flow.", + "Inspect PE/CLI metadata members, signatures, raw CIL hashes, decoded-instruction-tuple hashes, separately reported exception regions, call edges, and field-access anchors without loading or executing target code. Metadata tokens are reported as build-local coordinates bound to the artifact SHA-256 and MVID; the tuple hash does not resolve them or fully commit control flow.", kind: "managed-provider", inputSchema: managedMemberInputSchema, outputSchema: memberOutputSchema, diff --git a/src/contracts/managedWorkflowToolContracts.ts b/src/contracts/managedWorkflowToolContracts.ts index f089c2c386..902763162f 100644 --- a/src/contracts/managedWorkflowToolContracts.ts +++ b/src/contracts/managedWorkflowToolContracts.ts @@ -143,7 +143,7 @@ export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [ name: "compare_managed_members", ...toolContractMetadata("compare_managed_members"), description: - "Compare two authenticated inspect_managed_members Evidence records using unique-only decoded-CIL-v1/signature and structural method-shape tiers. Names are reported as observations but are not used as a matching basis; metadata tokens remain build-local coordinates bound to each artifact SHA-256 and MVID, and the v1 tuple digest does not itself remap them.", + "Compare two authenticated inspect_managed_members Evidence records using unique-only decoded-CIL/signature and structural method-shape tiers. Names are reported as observations but are not used as a matching basis; metadata tokens remain build-local coordinates bound to each artifact SHA-256 and MVID, and the tuple digest does not itself remap them.", kind: "application", inputSchema: compareManagedMembersReferenceInputSchema, outputSchema: comparisonOutputSchema, @@ -184,7 +184,7 @@ export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [ name: "import_managed_reconstruction", ...toolContractMetadata("import_managed_reconstruction"), description: - "Import decompiler-produced managed reconstruction against authenticated inspect_managed_members Evidence. The workflow locks each method to artifact SHA-256, MVID, metadata token, signature hash, and the limited member-result-v1 decoded-IL tuple hash, records the decompiler identity and options, and marks C# or pseudocode as analyst inference rather than canonical byte observation.", + "Import decompiler-produced managed reconstruction against authenticated inspect_managed_members Evidence. The workflow locks each method to artifact SHA-256, MVID, metadata token, signature hash, and the limited decoded-IL tuple hash, records the decompiler identity and options, and marks C# or pseudocode as analyst inference rather than canonical byte observation.", kind: "application", inputSchema: managedReconstructionReferenceInputSchema, outputSchema: reconstructionOutputSchema, @@ -205,7 +205,7 @@ export const MANAGED_WORKFLOW_TOOL_CONTRACTS = [ name: "plan_managed_runtime_correlation", ...toolContractMetadata("plan_managed_runtime_correlation"), description: - "Prepare a separately authorized managed runtime-correlation admission plan from authenticated inspect_managed_members Evidence. The operation is default-disabled, requires the managed_runtime permission ceiling and grant, locks the exact artifact SHA-256, MVID, method signature, and limited member-result-v1 decoded-IL tuple hash, distinguishes attach/load/debugger/reflection/instrumentation effects, and records that no target code was executed.", + "Prepare a separately authorized managed runtime-correlation admission plan from authenticated inspect_managed_members Evidence. The operation is default-disabled, requires the managed_runtime permission ceiling and grant, locks the exact artifact SHA-256, MVID, method signature, and limited decoded-IL tuple hash, distinguishes attach/load/debugger/reflection/instrumentation effects, and records that no target code was executed.", kind: "application", inputSchema: managedRuntimeCorrelationReferenceInputSchema, outputSchema: runtimeOutputSchema, diff --git a/src/contracts/reconstructionReadinessExample.ts b/src/contracts/reconstructionReadinessExample.ts index 41420d76ab..0b81cf60b3 100644 --- a/src/contracts/reconstructionReadinessExample.ts +++ b/src/contracts/reconstructionReadinessExample.ts @@ -16,7 +16,7 @@ const sourceEvidence = (variant: string) => version: "1", }, { - predicateType: "rea.reconstruction-readiness-fixture/v1", + predicateType: "rea.reconstruction-readiness-fixture", operation: "run_reconstruction_readiness_fixture", parameters: { variant }, result: { passed: true, variant }, diff --git a/src/domain/analysisSnapshot.ts b/src/domain/analysisSnapshot.ts index ad12d8aaea..4654de316d 100644 --- a/src/domain/analysisSnapshot.ts +++ b/src/domain/analysisSnapshot.ts @@ -144,7 +144,7 @@ export const snapshotTarget = ( architecture: target.architecture ?? null, }); -/** Build the immutable provider/profile binding used by snapshot v2. */ +/** Build the immutable provider/profile binding used by a cached snapshot. */ export const snapshotBinding = ( profile: AnalysisProfileCommitment, ): AnalysisSnapshotBinding => diff --git a/src/domain/artifactComparison.ts b/src/domain/artifactComparison.ts index 297ee3961f..ab6d2e3c0b 100644 --- a/src/domain/artifactComparison.ts +++ b/src/domain/artifactComparison.ts @@ -67,7 +67,7 @@ const artifactChangeSchema = z.object({ .string() .regex(/^art_[a-f0-9]{64}$/u) .nullable(), - evidence_links: z.array(evidenceIdSchema).min(2).max(200), + evidence_links: z.array(evidenceIdSchema).min(2), }); /** Deterministic artifact comparison with every change returned inline. */ diff --git a/src/domain/bundleComparison.ts b/src/domain/bundleComparison.ts index f9c5ec2aeb..061a26da81 100644 --- a/src/domain/bundleComparison.ts +++ b/src/domain/bundleComparison.ts @@ -79,8 +79,6 @@ type RecordPair = z.infer< typeof bundleComparisonInputSchema >["record_pairs"][number]; -const MAX_ENTITIES = 100_000; - /** Compare canonical bundle membership and only explicitly paired observations. */ export const compareBundles = ( leftInput: unknown, @@ -89,7 +87,6 @@ export const compareBundles = ( ): BundleComparisonResult => { const left = parseEvidenceBundle(leftInput); const right = parseEvidenceBundle(rightInput); - enforceBounds(left, right); const leftDigest = bundleDigest(left); const rightDigest = bundleDigest(right); const recordResult = compareRecords(left, right, recordPairs); @@ -303,16 +300,6 @@ const historyEvidenceIds = (history: readonly ResidualUnknown[]): string[] => ), ].sort(ascii); -const enforceBounds = (left: EvidenceBundle, right: EvidenceBundle): void => { - const total = - left.records.length + - right.records.length + - left.unknowns.length + - right.unknowns.length; - if (total > MAX_ENTITIES) - throw new TypeError(`Bundle comparison exceeds ${MAX_ENTITIES} entities`); -}; - const bundleDigest = (bundle: EvidenceBundle): string => createHash("sha256").update(serializeEvidenceBundle(bundle)).digest("hex"); diff --git a/src/domain/changedBehavior.test.ts b/src/domain/changedBehavior.test.ts index ac626c3e86..5932dd0c62 100644 --- a/src/domain/changedBehavior.test.ts +++ b/src/domain/changedBehavior.test.ts @@ -39,7 +39,7 @@ const comparison = ( { predicateType: process ? "rea.process-comparison" - : "rea.artifact-comparison/v1", + : "rea.artifact-comparison", operation, parameters: {}, result, @@ -230,7 +230,7 @@ describe("changed behavior", () => { undefined, { id: "rea-bundle-comparison", name: "Bundle", version: "1" }, { - predicateType: "rea.bundle-comparison/v1", + predicateType: "rea.bundle-comparison", operation: "compare_bundles", parameters: {}, result: {}, diff --git a/src/domain/changedBehavior.ts b/src/domain/changedBehavior.ts index edb15fa14b..2c9a5dba7d 100644 --- a/src/domain/changedBehavior.ts +++ b/src/domain/changedBehavior.ts @@ -89,13 +89,13 @@ const EXPECTED_PROVIDERS = { version: "3", }, compare_artifacts: { - predicates: ["rea.artifact-comparison/v1"], + predicates: ["rea.artifact-comparison"], id: "rea-artifact-comparison", name: "REA artifact comparison", version: "1", }, compare_functions: { - predicates: ["rea.function-comparison/v1"], + predicates: ["rea.function-comparison"], id: "rea-function-comparison", name: "REA function comparison", version: "1", diff --git a/src/domain/javascriptApplicationEvidenceSchemas.ts b/src/domain/javascriptApplicationEvidenceSchemas.ts index 3ddeb7ed69..4d9dee2cee 100644 --- a/src/domain/javascriptApplicationEvidenceSchemas.ts +++ b/src/domain/javascriptApplicationEvidenceSchemas.ts @@ -191,7 +191,7 @@ const applicationLimitSchema = z.strictObject({ }); /** Bounded collection coverage without treating omission as absence. */ -const coverageLimitsSchema = z.array(applicationLimitSchema).max(32); +const coverageLimitsSchema = z.array(applicationLimitSchema); const omittedCountSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER); export const applicationCoverageSchema = z.union([ @@ -211,7 +211,7 @@ export const applicationCoverageSchema = z.union([ status: z.literal("partial"), truncated: z.literal(true), omitted_count: z.union([omittedCountSchema.positive(), z.null()]), - limits: z.array(applicationLimitSchema).min(1).max(32), + limits: z.array(applicationLimitSchema).min(1), }), z.strictObject({ status: z.enum(["unknown", "unavailable"]), diff --git a/src/domain/javascriptAstFingerprint.ts b/src/domain/javascriptAstFingerprint.ts index 9c185ac037..3d382c9fcb 100644 --- a/src/domain/javascriptAstFingerprint.ts +++ b/src/domain/javascriptAstFingerprint.ts @@ -8,10 +8,9 @@ export interface AstFingerprint { readonly truncated: boolean; } -/** Bounded static CommonJS export names. */ +/** Static CommonJS export names. */ export interface StaticExports { readonly values: readonly string[]; - readonly truncated: boolean; } /** Derive a rename-resistant, bounded syntax fingerprint without code execution. */ @@ -41,31 +40,16 @@ export const fingerprintJavaScriptAst = ( }; /** Collect statically declared CommonJS/bundler export names from one factory. */ -export const collectJavaScriptExports = ( - node: t.Node, - maximumExports: number, -): StaticExports => { +export const collectJavaScriptExports = (node: t.Node): StaticExports => { const exports = new Set(); - let truncated = false; t.traverseFast(node, (current) => { - if (exports.size >= maximumExports) { - truncated = true; - return t.traverseFast.stop; - } if (t.isAssignmentExpression(current)) - collectAssignmentExports( - current.left, - current.right, - exports, - maximumExports, - ); - if (t.isCallExpression(current)) - collectCallExports(current, exports, maximumExports); + collectAssignmentExports(current.left, current.right, exports); + if (t.isCallExpression(current)) collectCallExports(current, exports); return undefined; }); return { values: [...exports].sort(compareCodePoints), - truncated, }; }; @@ -97,31 +81,29 @@ const collectAssignmentExports = ( left: t.LVal | t.OptionalMemberExpression, right: t.Expression, output: Set, - maximum: number, ): void => { const path = memberPath(left); if (path === "exports" || path === "module.exports") { - if (t.isObjectExpression(right)) collectObjectKeys(right, output, maximum); - else addExport(output, "default", maximum); + if (t.isObjectExpression(right)) collectObjectKeys(right, output); + else addExport(output, "default"); return; } if (path.startsWith("exports.")) - addExport(output, path.slice("exports.".length), maximum); + addExport(output, path.slice("exports.".length)); if (path.startsWith("module.exports.")) - addExport(output, path.slice("module.exports.".length), maximum); + addExport(output, path.slice("module.exports.".length)); }; const collectCallExports = ( call: t.CallExpression, output: Set, - maximum: number, ): void => { const callee = memberPath(call.callee); if (callee === "Object.defineProperty") { const target = memberPath(call.arguments[0]); const name = stringValue(call.arguments[1]); if ((target === "exports" || target === "module.exports") && name) - addExport(output, name, maximum); + addExport(output, name); } if (!callee.endsWith(".d")) return; const target = memberPath(call.arguments[0]); @@ -130,28 +112,22 @@ const collectCallExports = ( (target === "exports" || target === "module.exports") && t.isObjectExpression(declarations) ) - collectObjectKeys(declarations, output, maximum); + collectObjectKeys(declarations, output); }; const collectObjectKeys = ( object: t.ObjectExpression, output: Set, - maximum: number, ): void => { for (const property of object.properties) { - if (output.size >= maximum) return; if (!t.isObjectProperty(property) && !t.isObjectMethod(property)) continue; const name = propertyName(property.key); - if (name !== "") addExport(output, name, maximum); + if (name !== "") addExport(output, name); } }; -const addExport = ( - output: Set, - name: string, - maximum: number, -): void => { - if (output.size < maximum) output.add(name.slice(0, 4_096)); +const addExport = (output: Set, name: string): void => { + output.add(name); }; const memberPath = (node: t.Node | null | undefined, depth = 0): string => { diff --git a/src/domain/javascriptExportShapeComparisonSchemas.test.ts b/src/domain/javascriptExportShapeComparisonSchemas.test.ts index 3ab413e858..7c22af6c21 100644 --- a/src/domain/javascriptExportShapeComparisonSchemas.test.ts +++ b/src/domain/javascriptExportShapeComparisonSchemas.test.ts @@ -1,6 +1,9 @@ import { expect, it } from "vitest"; -import { projectedExportReturnShapesSchema } from "./javascriptExportShapeComparisonSchemas.js"; +import { + javaScriptExportShapeComparisonResultSchema, + projectedExportReturnShapesSchema, +} from "./javascriptExportShapeComparisonSchemas.js"; const projectionWithField = (field: unknown) => ({ semantic_role: "export-return-shapes", @@ -51,3 +54,109 @@ it("parses projected fields into literal, union, or unknown values", () => { ).success, ).toBe(true); }); + +it("accepts every projected return site and field without presentation caps", () => { + const fields = Array.from({ length: 65 }, (_, index) => ({ + path: `/field-${String(index)}`, + state: "literal", + value: index, + reason: null, + })); + const propertyCoverage = fields.map(({ path }) => ({ + path, + status: "complete", + omitted: 0, + })); + const shapes = Array.from({ length: 33 }, () => ({ + source_range: { + start: { line: 1, column: 0 }, + end: { line: 1, column: 10 }, + }, + value_status: "object", + fields, + property_coverage: propertyCoverage, + })); + + expect( + projectedExportReturnShapesSchema.parse({ + ...projectionWithField(fields[0]), + static_return_shapes: shapes, + return_shape_coverage: { + status: "complete", + retained_return_sites: shapes.length, + omitted_return_sites: 0, + omitted_fields: 0, + omitted_property_coverage: 0, + projection_complete: true, + }, + }).static_return_shapes, + ).toHaveLength(33); +}); + +it("accepts complete candidate and change inventories without page ceilings", () => { + const evidenceId = `ev_${"a".repeat(64)}`; + const graphId = `jag_${"b".repeat(64)}`; + const selector = { + evidence_id: evidenceId, + graph_id: graphId, + requested_module_path: "index.js", + requested_export_name: "render", + candidates: Array.from({ length: 1_001 }, (_, index) => ({ + node_id: `jag_node_${index.toString(16).padStart(64, "0")}`, + module_path: "index.js", + export_name: "render", + matches_requested_module: true, + matches_requested_export: true, + })), + omitted_candidates: 0, + status: "missing", + selected_node_id: null, + }; + const changes = Array.from({ length: 10_001 }, (_, index) => ({ + change_id: `jesc_change_${index.toString(16).padStart(64, "0")}`, + status: "unknown", + path: `/field-${String(index)}`, + discriminant: null, + left: { availability: "absent" }, + right: { availability: "absent" }, + left_source_range: null, + right_source_range: null, + evidence_links: [evidenceId, `ev_${"c".repeat(64)}`], + limitations: [], + })); + + expect( + javaScriptExportShapeComparisonResultSchema.parse({ + comparison_id: `jesc_${"d".repeat(64)}`, + left: selector, + right: { ...selector, graph_id: `jag_${"e".repeat(64)}` }, + summary: { added: 0, removed: 0, changed: 0, unknown: changes.length }, + changes, + coverage: { + status: "complete-within-inputs", + left_graph_status: "complete", + right_graph_status: "complete", + paired_variants: 0, + unpaired_left_variants: 0, + unpaired_right_variants: 0, + omitted_left_variants: 0, + omitted_right_variants: 0, + left_source_omitted_variants: 0, + right_source_omitted_variants: 0, + left_omitted_fields: 0, + right_omitted_fields: 0, + left_omitted_property_coverage: 0, + right_omitted_property_coverage: 0, + omitted_candidates: 0, + omitted_changes: 0, + }, + evidence_links: [evidenceId, `ev_${"c".repeat(64)}`], + limitations: [], + runtime_validation: { + recommended_tool: "run_controlled_replay", + automatically_started: false, + required_for: "runtime-semantics", + }, + }).changes, + ).toHaveLength(10_001); +}); diff --git a/src/domain/javascriptExportShapeComparisonSchemas.ts b/src/domain/javascriptExportShapeComparisonSchemas.ts index 24aa02de19..3edda08c78 100644 --- a/src/domain/javascriptExportShapeComparisonSchemas.ts +++ b/src/domain/javascriptExportShapeComparisonSchemas.ts @@ -84,7 +84,7 @@ const projectedReturnFieldSchema = z.discriminatedUnion("state", [ z.strictObject({ ...projectedReturnFieldShape, state: z.literal("union"), - value: z.array(semanticPrimitiveSchema).min(1).max(32), + value: z.array(semanticPrimitiveSchema).min(1), reason: z.null(), }), z.strictObject({ @@ -125,8 +125,8 @@ const projectedReturnShapeSchema = z "cycle", "limit-reached", ]), - fields: z.array(projectedReturnFieldSchema).max(64), - property_coverage: z.array(projectedPropertyCoverageSchema).max(64), + fields: z.array(projectedReturnFieldSchema), + property_coverage: z.array(projectedPropertyCoverageSchema), }) .superRefine((shape, context) => { const fieldPaths = shape.fields.map(({ path }) => path); @@ -153,10 +153,10 @@ export const projectedExportReturnShapesSchema = z exported_name: selectorTextSchema, callable_id: boundedTextSchema, callable_kind: z.enum(["function", "class", "method"]), - static_return_shapes: z.array(projectedReturnShapeSchema).max(32), + static_return_shapes: z.array(projectedReturnShapeSchema), return_shape_coverage: z.strictObject({ status: z.enum(["complete", "partial", "truncated"]), - retained_return_sites: z.number().int().min(0).max(32), + retained_return_sites: z.number().int().min(0), omitted_return_sites: z.number().int().min(0).nullable(), omitted_fields: z.number().int().min(0), omitted_property_coverage: z.number().int().min(0), @@ -188,7 +188,7 @@ const selectorResultShape = { graph_id: graphIdSchema, requested_module_path: selectorTextSchema, requested_export_name: selectorTextSchema, - candidates: z.array(exportCandidateSchema).max(1_000), + candidates: z.array(exportCandidateSchema), omitted_candidates: z.number().int().min(0), }; const selectorResultSchema = z.union([ @@ -217,7 +217,7 @@ const valueAvailabilitySchema = z.discriminatedUnion("availability", [ }), z.strictObject({ availability: z.literal("union"), - values: z.array(semanticPrimitiveSchema).min(1).max(32), + values: z.array(semanticPrimitiveSchema).min(1), }), z.strictObject({ availability: z.literal("unknown"), @@ -240,7 +240,7 @@ const comparisonChangeSchema = z.strictObject({ left_source_range: sourceRangeSchema.nullable(), right_source_range: sourceRangeSchema.nullable(), evidence_links: z.array(evidenceIdSchema).length(2), - limitations: z.array(boundedTextSchema).max(20), + limitations: z.array(boundedTextSchema), }); /** Bounded static export-return comparison with explicit unknown semantics. */ @@ -254,7 +254,7 @@ export const javaScriptExportShapeComparisonResultSchema = z.strictObject({ changed: z.number().int().min(0), unknown: z.number().int().min(0), }), - changes: z.array(comparisonChangeSchema).max(10_000), + changes: z.array(comparisonChangeSchema), coverage: z.strictObject({ status: z.enum(["complete-within-inputs", "partial", "truncated"]), left_graph_status: z.enum([ @@ -284,7 +284,7 @@ export const javaScriptExportShapeComparisonResultSchema = z.strictObject({ omitted_changes: z.number().int().min(0), }), evidence_links: z.array(evidenceIdSchema).length(2), - limitations: z.array(boundedTextSchema).max(1_000), + limitations: z.array(boundedTextSchema), runtime_validation: z.strictObject({ recommended_tool: z.literal("run_controlled_replay"), automatically_started: z.literal(false), diff --git a/src/domain/javascriptRuntimeReconciliationActive.ts b/src/domain/javascriptRuntimeReconciliationActive.ts index e69e35f1f8..2ced79fe94 100644 --- a/src/domain/javascriptRuntimeReconciliationActive.ts +++ b/src/domain/javascriptRuntimeReconciliationActive.ts @@ -55,7 +55,7 @@ const absolutePathSchema = z.string().min(1).max(16_384).refine(isAbsolute); const assertIdentity = (evidence: Evidence): void => { if ( evidence.operation !== "capture_electron_scenario" || - evidence.predicate_type !== "rea.electron-active-scenario/v1" || + evidence.predicate_type !== "rea.electron-active-scenario" || evidence.provider.id !== "rea-playwright-electron-active" || evidence.provider.name !== "REA Playwright active Electron observation provider" || diff --git a/src/domain/javascriptRuntimeReconciliationParsing.ts b/src/domain/javascriptRuntimeReconciliationParsing.ts index 432748e624..7716196b1a 100644 --- a/src/domain/javascriptRuntimeReconciliationParsing.ts +++ b/src/domain/javascriptRuntimeReconciliationParsing.ts @@ -187,7 +187,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => { if (evidence.operation === "inspect_web_page") { assertEvidenceIdentity(evidence, { operation: "inspect_web_page", - predicate: "rea.web-page-inspection/v2", + predicate: "rea.web-page-inspection", providerId: "rea-cdp-browser", providerName: "REA Chrome DevTools Protocol observation provider", providerVersion: "2", @@ -216,7 +216,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => { if (evidence.operation === "inspect_electron_page") { assertEvidenceIdentity(evidence, { operation: "inspect_electron_page", - predicate: "rea.electron-page-inspection/v1", + predicate: "rea.electron-page-inspection", providerId: "rea-cdp-electron", providerName: "REA Electron file-page CDP observation provider", providerVersion: "1", @@ -244,7 +244,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => { if (evidence.operation === "observe_javascript_runtime") { assertEvidenceIdentity(evidence, { operation: "observe_javascript_runtime", - predicate: "rea.javascript-runtime-observation/v1", + predicate: "rea.javascript-runtime-observation", providerId: "rea-v8-inspector", providerName: "REA passive Node/Electron V8 Inspector provider", providerVersion: "1", diff --git a/src/domain/javascriptSemanticAnalysis.calls.test.ts b/src/domain/javascriptSemanticAnalysis.calls.test.ts index ad6f9ea82b..1c701966b3 100644 --- a/src/domain/javascriptSemanticAnalysis.calls.test.ts +++ b/src/domain/javascriptSemanticAnalysis.calls.test.ts @@ -156,21 +156,17 @@ describe("JavaScript semantic analysis: calls 2", () => { ]); }); - it("bounds Promise recovery with an explicit frontier", () => { + it("retains every promise operation found in the finite source", () => { const ir = analyzeJavaScriptSemantics( ` Promise.resolve(1); Promise.resolve(2); Promise.resolve(3); `, - { maxPromiseOperations: 1 }, ); - expect(ir.promiseOperations).toHaveLength(1); - expect(ir.coverage).toMatchObject({ - status: "truncated", - limitsReached: ["maxPromiseOperations"], - }); + expect(ir.promiseOperations).toHaveLength(3); + expect(ir.coverage.status).toBe("complete"); }); it("fingerprints formatting and local-name changes identically", () => { diff --git a/src/domain/javascriptSemanticAnalysis.dataflow.test.ts b/src/domain/javascriptSemanticAnalysis.dataflow.test.ts index e201c70626..b4412e057a 100644 --- a/src/domain/javascriptSemanticAnalysis.dataflow.test.ts +++ b/src/domain/javascriptSemanticAnalysis.dataflow.test.ts @@ -48,7 +48,7 @@ describe("JavaScript semantic analysis: dataflow 1", () => { ]); }); - it("applies independent hard bounds to every new semantic fact family", () => { + it("retains all statically recovered call, flow, capture, and frontier facts", () => { const ir = analyzeJavaScriptSemantics( ` const captured = 1; @@ -58,35 +58,24 @@ describe("JavaScript semantic analysis: dataflow 1", () => { object[first]; object[second]; `, - { - maxCallSites: 1, - maxCallArguments: 1, - maxArgumentFlows: 0, - maxCallReturnFlows: 0, - maxClosureCaptures: 0, - maxFrontiers: 1, - }, ); - expect(ir.callSites).toHaveLength(1); - expect(ir.callSites[0]?.arguments).toEqual([ - expect.objectContaining({ index: 0, spread: false }), + expect(ir.callSites).toHaveLength(2); + expect(ir.callSites.map(({ arguments: args }) => args)).toEqual([ + [ + expect.objectContaining({ index: 0, spread: false }), + expect.objectContaining({ index: 1, spread: false }), + ], + [ + expect.objectContaining({ index: 0, spread: false }), + expect.objectContaining({ index: 1, spread: false }), + ], ]); - expect(ir.argumentFlows).toEqual([]); - expect(ir.callReturnFlows).toEqual([]); - expect(ir.closureCaptures).toEqual([]); - expect(ir.frontiers).toHaveLength(1); - expect(ir.coverage).toMatchObject({ - status: "truncated", - limitsReached: expect.arrayContaining([ - "maxCallSites", - "maxCallArguments", - "maxArgumentFlows", - "maxCallReturnFlows", - "maxClosureCaptures", - "maxFrontiers", - ]), - }); + expect(ir.argumentFlows).toHaveLength(4); + expect(ir.callReturnFlows).toHaveLength(2); + expect(ir.closureCaptures).toHaveLength(1); + expect(ir.frontiers).toHaveLength(2); + expect(ir.coverage.status).toBe("complete"); }); it("recovers only direct return sites with literal and unknown object fields", () => { @@ -137,7 +126,7 @@ describe("JavaScript semantic analysis: dataflow 1", () => { }); describe("JavaScript semantic analysis: dataflow 2", () => { - it("retains partial property coverage, empty returns, and return limits", () => { + it("retains partial property coverage and empty returns", () => { const partial = analyzeJavaScriptSemantics(` const spread = () => ({ type: "spread", ...dynamic }); const computed = () => ({ type: "computed", [key]: 1 }); @@ -163,24 +152,9 @@ describe("JavaScript semantic analysis: dataflow 2", () => { status: "unknown", reason: "Return has no value.", }); - - const limited = analyzeJavaScriptSemantics( - "function many(value) { if (value) return 1; return 2; }", - { maxReturnSites: 1 }, - ); - expect(onlyCallable(limited, "many")).toMatchObject({ - returnSites: [{ value: { status: "literal", value: 1 } }], - returnCoverage: { - status: "truncated", - retainedCount: 1, - omittedCount: 1, - limitsReached: ["maxReturnSites"], - }, - }); - expect(limited.coverage.limitsReached).toContain("maxReturnSites"); }); - it("fails closed on assignment ambiguity, alias cycles, and lattice limits", () => { + it("fails closed on assignment ambiguity and alias cycles", () => { const ambiguous = analyzeJavaScriptSemantics(` let current = "first"; current = "second"; @@ -196,20 +170,6 @@ describe("JavaScript semantic analysis: dataflow 2", () => { expect(topLevelBinding(ambiguous, "right").provenance).toMatchObject({ status: "cycle", }); - - const limited = analyzeJavaScriptSemantics( - 'const channel = enabled ? "one" : "two";', - { maxUnionValues: 1 }, - ); - expect(topLevelBinding(limited, "channel").value).toEqual({ - status: "limit-reached", - reason: "maxUnionValues reached.", - }); - expect(limited.coverage).toMatchObject({ - status: "truncated", - limitsReached: ["maxUnionValues"], - }); - expect(limited.coverage.omittedCount).toBeGreaterThan(0); }); it("does not invent exact module paths for dynamic property access", () => { @@ -234,7 +194,7 @@ describe("JavaScript semantic analysis: dataflow 2", () => { ).toEqual([]); }); - it("bounds retained scopes and bindings without corrupting outer resolution", () => { + it("retains all scopes, bindings, and references without truncation", () => { const ir = analyzeJavaScriptSemantics( ` const retained = "yes"; @@ -242,23 +202,22 @@ describe("JavaScript semantic analysis: dataflow 2", () => { retained; function nested(value) { return retained + value; } `, - { maxBindings: 1, maxCallables: 0, maxScopes: 1 }, ); - expect(ir.bindings.map(({ name }) => name)).toEqual(["retained"]); - expect(ir.callables).toEqual([]); - expect(ir.coverage.status).toBe("truncated"); - expect(ir.coverage.limitsReached).toEqual( - expect.arrayContaining(["maxBindings", "maxCallables", "maxScopes"]), - ); - expect(ir.references.filter(({ name }) => name === "retained")).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - resolution: "resolved", - bindingId: topLevelBinding(ir, "retained").bindingId, - }), - expect.objectContaining({ resolution: "unknown", bindingId: null }), - ]), + expect(ir.bindings.map(({ name }) => name)).toEqual( + expect.arrayContaining(["retained", "omitted", "value"]), ); + expect(ir.callables.map(({ name }) => name)).toContain("nested"); + expect(ir.coverage.status).toBe("complete"); + expect(ir.references.filter(({ name }) => name === "retained")).toEqual([ + expect.objectContaining({ + resolution: "resolved", + bindingId: topLevelBinding(ir, "retained").bindingId, + }), + expect.objectContaining({ + resolution: "resolved", + bindingId: topLevelBinding(ir, "retained").bindingId, + }), + ]); }); }); diff --git a/src/domain/javascriptSemanticAnalysis.rejection.test.ts b/src/domain/javascriptSemanticAnalysis.rejection.test.ts index e86e589615..e7baa098bd 100644 --- a/src/domain/javascriptSemanticAnalysis.rejection.test.ts +++ b/src/domain/javascriptSemanticAnalysis.rejection.test.ts @@ -8,31 +8,23 @@ import { } from "./javascriptSemanticAnalysis.fixture.js"; describe("JavaScript semantic analysis: rejection 1", () => { - it("reports reference, module-link, depth, and object-property limits", () => { - const retained = analyzeJavaScriptSemantics( - ` - const first = require("first"); - const second = require("second"); - first; - second; - `, - { maxModuleLinks: 1, maxReferences: 1 }, + it("retains complete finite facts and reports the recursive depth guard", () => { + const names = Array.from( + { length: 280 }, + (_, index) => `key${String(index)}`, ); - expect(retained.moduleLinks).toHaveLength(1); - expect(retained.references).toHaveLength(1); - expect(retained.coverage.limitsReached).toEqual( - expect.arrayContaining(["maxModuleLinks", "maxReferences"]), - ); - const object = analyzeJavaScriptSemantics( - "const object = { first: 1, second: 2 };", - { maxObjectProperties: 1 }, + `const first = require("first"); const second = require("second"); first; second; const object = { ${names.map((name, index) => `${name}: ${String(index)}`).join(", ")} };`, ); + expect(object.moduleLinks).toHaveLength(2); + expect(object.references).toHaveLength(4); expect(topLevelBinding(object, "object").value).toMatchObject({ status: "object", - unknownProperties: true, + unknownProperties: false, + properties: expect.arrayContaining([ + expect.objectContaining({ name: "key279" }), + ]), }); - expect(object.coverage.limitsReached).toContain("maxObjectProperties"); const depth = analyzeJavaScriptSemantics( 'const first = "value"; const second = first;', @@ -104,15 +96,9 @@ describe("JavaScript semantic analysis: rejection 1", () => { }); it.prop([fc.string({ maxLength: 512 })])( - "fails closed for arbitrary bounded source text", + "fails closed for arbitrary source text", (source) => { - const ir = analyzeJavaScriptSemantics(source, { - maxBindings: 64, - maxCallables: 64, - maxModuleLinks: 64, - maxReferences: 256, - maxScopes: 64, - }); + const ir = analyzeJavaScriptSemantics(source); expect(ir.schema).toBe("JavaScriptSemanticIR"); expect(["complete", "partial", "truncated", "failed"]).toContain( diff --git a/src/domain/javascriptSemanticAnalysis.runtime_boundaries.test.ts b/src/domain/javascriptSemanticAnalysis.runtime_boundaries.test.ts index 9ba3e5df09..dba9a957ac 100644 --- a/src/domain/javascriptSemanticAnalysis.runtime_boundaries.test.ts +++ b/src/domain/javascriptSemanticAnalysis.runtime_boundaries.test.ts @@ -85,7 +85,7 @@ describe("JavaScript semantic analysis: runtime boundaries 1", () => { expect(ir.timerOperations).toEqual([]); }); - it("bounds every added semantic effect family independently", () => { + it("retains every added semantic effect family", () => { const ir = analyzeJavaScriptSemantics( ` import { spawn } from "node:child_process"; @@ -99,31 +99,17 @@ describe("JavaScript semantic analysis: runtime boundaries 1", () => { JSON.parse(raw); open(path); `, - { - maxPromiseOperations: 0, - maxEventOperations: 0, - maxTimerOperations: 0, - maxChildProcessOperations: 0, - maxConfigurationOperations: 0, - maxRequestOperations: 0, - maxBoundaryOperations: 0, - maxResourceOperations: 0, - maxObjectOperations: 0, - }, ); - expect(ir.coverage.limitsReached).toEqual( - expect.arrayContaining([ - "maxBoundaryOperations", - "maxChildProcessOperations", - "maxConfigurationOperations", - "maxEventOperations", - "maxPromiseOperations", - "maxRequestOperations", - "maxResourceOperations", - "maxTimerOperations", - "maxObjectOperations", - ]), - ); + expect(ir.coverage.status).toBe("complete"); + expect(ir.promiseOperations).toHaveLength(1); + expect(ir.eventOperations).toHaveLength(1); + expect(ir.timerOperations).toHaveLength(1); + expect(ir.childProcessSpawns).toHaveLength(1); + expect(ir.configurationOperations).toHaveLength(1); + expect(ir.requestOperations).toHaveLength(1); + expect(ir.boundaryOperations).toHaveLength(1); + expect(ir.resourceOperations).toHaveLength(1); + expect(ir.objectOperations.length).toBeGreaterThan(0); }); }); diff --git a/src/domain/javascriptSemanticAnalysis.ts b/src/domain/javascriptSemanticAnalysis.ts index 72d6cb0954..91859c392e 100644 --- a/src/domain/javascriptSemanticAnalysis.ts +++ b/src/domain/javascriptSemanticAnalysis.ts @@ -14,7 +14,6 @@ import { collectSemanticReferences, immutableSemanticBindings, immutableSemanticScopes, - reachSemanticLimit, semanticModuleOrigin, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -249,17 +248,6 @@ const nestedScope = ( ): JavaScriptSemanticScopeState | undefined => { const kind = scopeKind(node, parent); if (kind === undefined) return undefined; - if (state.scopes.length >= state.limits.maxScopes) { - reachSemanticLimit(state, "maxScopes"); - return { - scopeId: `${semanticScopeId(kind, node)}:omitted`, - parentScopeId: parentScope.scopeId, - kind, - location: range(node), - bindingsComplete: false, - bindings: new Map(), - }; - } const scope: JavaScriptSemanticScopeState = { scopeId: semanticScopeId(kind, node), parentScopeId: parentScope.scopeId, @@ -447,11 +435,6 @@ const addBinding = (input: AddBindingInput): void => { state.omittedCount += 1; return; } - if (state.bindingsById.size >= state.limits.maxBindings) { - scope.bindingsComplete = false; - reachSemanticLimit(state, "maxBindings"); - return; - } binding = createBinding(scope, name, kind, mutable); scope.bindings.set(name, binding); state.bindingsById.set(binding.bindingId, binding); diff --git a/src/domain/javascriptSemanticAsyncEffects.ts b/src/domain/javascriptSemanticAsyncEffects.ts index bfbc727423..1f765f80fd 100644 --- a/src/domain/javascriptSemanticAsyncEffects.ts +++ b/src/domain/javascriptSemanticAsyncEffects.ts @@ -6,7 +6,6 @@ import type { JavaScriptSemanticTimerOperation, } from "./javascriptSemanticIr.js"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -116,10 +115,6 @@ const collectEvent = ( ); if (method === undefined) return; const kind = eventKind(method); - if (output.length >= state.limits.maxEventOperations) { - reachSemanticLimit(state, "maxEventOperations"); - return; - } const eventArgument = node.arguments[0]; const listenerArgument = node.arguments[1]; const emitter = emitterIdentity(member.object, state); @@ -191,10 +186,6 @@ const collectTimer = (input: TimerCollectionInput): void => { const { node, parent, ownerCallableId, state, output } = input; const method = timerMethod(node.callee, state); if (method === null) return; - if (output.length >= state.limits.maxTimerOperations) { - reachSemanticLimit(state, "maxTimerOperations"); - return; - } const kind = TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method) ? "schedule" : "cancel"; diff --git a/src/domain/javascriptSemanticCalls.ts b/src/domain/javascriptSemanticCalls.ts index 5a11ecdf99..b4a1be0a8b 100644 --- a/src/domain/javascriptSemanticCalls.ts +++ b/src/domain/javascriptSemanticCalls.ts @@ -10,7 +10,6 @@ import type { JavaScriptSemanticFrontier, } from "./javascriptSemanticIr.js"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticReferenceRole, semanticStaticPropertyName, @@ -45,7 +44,6 @@ interface MutableCallAnalysis { readonly callResultFlows: JavaScriptSemanticCallResultFlow[]; readonly closureCaptures: JavaScriptSemanticClosureCapture[]; readonly frontiers: JavaScriptSemanticFrontier[]; - retainedArguments: number; } interface CallCollectionContext { @@ -59,7 +57,7 @@ interface CallCollectionContext { >; } -/** Recover bounded direct-call and lexical-capture candidates from inert syntax. */ +/** Recover direct-call and lexical-capture candidates from inert syntax. */ export const collectJavaScriptSemanticCalls = ( program: t.Program, state: JavaScriptSemanticAnalysisState, @@ -72,7 +70,6 @@ export const collectJavaScriptSemanticCalls = ( callResultFlows: [], closureCaptures: [], frontiers: [], - retainedArguments: 0, }; const callableById = new Map( callables.map((callable) => [callable.callableId, callable]), @@ -127,10 +124,6 @@ const collectCallSite = ( context: CallCollectionContext, ): void => { const { state, callableById, output } = context; - if (output.callSites.length >= state.limits.maxCallSites) { - reachSemanticLimit(state, "maxCallSites"); - return; - } const resolution = resolveLocalCallables({ node: node.callee, state, @@ -140,7 +133,7 @@ const collectCallSite = ( bindingCache: context.bindingResolutionCache, }); const callSiteId = semanticCallSiteId(node); - const argumentsValue = retainedArguments(node.arguments, state, output); + const argumentsValue = retainedArguments(node.arguments); const site: JavaScriptSemanticCallSite = { callSiteId, kind: t.isNewExpression(node) ? "construct" : "call", @@ -219,16 +212,9 @@ const retainedArguments = ( | t.JSXNamespacedName | t.ArgumentPlaceholder )[], - state: JavaScriptSemanticAnalysisState, - output: MutableCallAnalysis, ): JavaScriptSemanticCallSite["arguments"] => { const retained: JavaScriptSemanticCallSite["arguments"][number][] = []; for (const [index, node] of nodes.entries()) { - if (output.retainedArguments >= state.limits.maxCallArguments) { - reachSemanticLimit(state, "maxCallArguments"); - break; - } - output.retainedArguments += 1; retained.push({ index, location: range(node), @@ -243,7 +229,7 @@ const collectArgumentFlows = ( node: t.CallExpression | t.OptionalCallExpression | t.NewExpression, context: CallCollectionContext, ): void => { - const { state, callableById, output } = context; + const { callableById, output } = context; const retainedIndexes = new Set(site.arguments.map(({ index }) => index)); let positionIsExact = true; for (const [index, argument] of node.arguments.entries()) { @@ -265,10 +251,6 @@ const collectArgumentFlows = ( index, context, )) { - if (output.argumentFlows.length >= state.limits.maxArgumentFlows) { - reachSemanticLimit(state, "maxArgumentFlows"); - return; - } const definition = parameter.definitions.find( ({ kind }) => kind === "parameter", ); @@ -309,10 +291,6 @@ const collectReturnFlows = ( const callable = callableById.get(callableId); if (callable === undefined) continue; for (const returnSite of callable.returnSites) { - if (output.callReturnFlows.length >= state.limits.maxCallReturnFlows) { - reachSemanticLimit(state, "maxCallReturnFlows"); - return; - } output.callReturnFlows.push({ callSiteId: site.callSiteId, callableId, @@ -344,10 +322,6 @@ const collectCapture = ( bindingIsWithinCallable(binding, owner.bodyScopeId, state) ) return; - if (output.closureCaptures.length >= state.limits.maxClosureCaptures) { - reachSemanticLimit(state, "maxClosureCaptures"); - return; - } output.closureCaptures.push({ callableId: owner.callableId, bindingId: binding.bindingId, @@ -397,10 +371,6 @@ const addFrontier = ( state: JavaScriptSemanticAnalysisState, output: MutableCallAnalysis, ): void => { - if (output.frontiers.length >= state.limits.maxFrontiers) { - reachSemanticLimit(state, "maxFrontiers"); - return; - } output.frontiers.push(frontier); }; diff --git a/src/domain/javascriptSemanticChildProcesses.ts b/src/domain/javascriptSemanticChildProcesses.ts index d8e0cd1317..05bb0528df 100644 --- a/src/domain/javascriptSemanticChildProcesses.ts +++ b/src/domain/javascriptSemanticChildProcesses.ts @@ -6,7 +6,6 @@ import type { JavaScriptSemanticChildProcessSpawn, } from "./javascriptSemanticIr.js"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -83,7 +82,6 @@ const collectSpawns = ( return; const method = childProcessMethod(node.callee, state); if (method === null) return; - if (operationLimitReached(output.length, state)) return; output.push({ node, method, @@ -151,7 +149,6 @@ const collectInteractions = ( context, ); if (interaction === null) return; - if (operationLimitReached(spawns.length + output.length, state)) return; output.push(interaction); }, exit: (node) => popCallable(node, callableStack), @@ -366,15 +363,6 @@ const literalString = ( return null; }; -const operationLimitReached = ( - retained: number, - state: JavaScriptSemanticAnalysisState, -): boolean => { - if (retained < state.limits.maxChildProcessOperations) return false; - reachSemanticLimit(state, "maxChildProcessOperations"); - return true; -}; - const popCallable = (node: t.Node, stack: string[]): void => { const callableId = semanticCallableIdForNode(node); if (callableId !== null && stack.at(-1) === callableId) stack.pop(); diff --git a/src/domain/javascriptSemanticCoverage.ts b/src/domain/javascriptSemanticCoverage.ts index 50031c28aa..78be8369d6 100644 --- a/src/domain/javascriptSemanticCoverage.ts +++ b/src/domain/javascriptSemanticCoverage.ts @@ -5,23 +5,11 @@ import { compareCodePoints } from "./javascriptStaticAnalysisHelpers.js"; /** Exact retention state for one callable's direct returns. */ export type JavaScriptSemanticReturnCoverage = { readonly retainedCount: number; -} & ( - | { - readonly status: "complete"; - readonly omittedCount: 0; - readonly limitsReached: readonly []; - } - | { - readonly status: "partial"; - readonly omittedCount: 0 | null; - readonly limitsReached: readonly []; - } - | { - readonly status: "truncated"; - readonly omittedCount: number; - readonly limitsReached: readonly ["maxReturnSites"]; - } -); +} & { + readonly status: "complete" | "partial"; + readonly omittedCount: 0 | null; + readonly limitsReached: readonly []; +}; /** Coverage state for one bounded semantic recovery pass. */ export type JavaScriptSemanticCoverage = @@ -69,19 +57,10 @@ export const semanticCoverage = ( /** Classify one callable's direct-return coverage. */ export const semanticReturnCoverage = ( retainedCount: number, - omittedCount: number, parserPartial: boolean, -): JavaScriptSemanticReturnCoverage => - omittedCount > 0 - ? { - status: "truncated", - retainedCount, - omittedCount, - limitsReached: ["maxReturnSites"], - } - : { - status: parserPartial ? "partial" : "complete", - retainedCount, - omittedCount: 0, - limitsReached: [], - }; +): JavaScriptSemanticReturnCoverage => ({ + status: parserPartial ? "partial" : "complete", + retainedCount, + omittedCount: 0, + limitsReached: [], +}); diff --git a/src/domain/javascriptSemanticDataEffectHelpers.ts b/src/domain/javascriptSemanticDataEffectHelpers.ts index 315b6ff9d4..4304d04b96 100644 --- a/src/domain/javascriptSemanticDataEffectHelpers.ts +++ b/src/domain/javascriptSemanticDataEffectHelpers.ts @@ -1,7 +1,6 @@ import * as t from "@babel/types"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -206,19 +205,3 @@ export const containsSemanticNode = (outer: t.Node, inner: t.Node): boolean => inner.end !== undefined && outer.start <= inner.start && outer.end >= inner.end; - -/** Record one bounded data-effect frontier. */ -export const dataEffectLimitReached = ( - retained: number, - limit: number, - name: - | "maxConfigurationOperations" - | "maxRequestOperations" - | "maxBoundaryOperations" - | "maxResourceOperations", - state: JavaScriptSemanticAnalysisState, -): boolean => { - if (retained < limit) return false; - reachSemanticLimit(state, name); - return true; -}; diff --git a/src/domain/javascriptSemanticDataEffects.ts b/src/domain/javascriptSemanticDataEffects.ts index 64030cef75..782b77ddec 100644 --- a/src/domain/javascriptSemanticDataEffects.ts +++ b/src/domain/javascriptSemanticDataEffects.ts @@ -16,7 +16,6 @@ import { builtinDataEffectMethod as builtinMethod, containsSemanticNode as containsNode, dataEffectArgumentBindingId as argumentBindingId, - dataEffectLimitReached as limitReached, dataEffectLiteralString as literalString, dataEffectMemberCallee as memberCallee, dataEffectMemberObject as memberObject, @@ -83,15 +82,6 @@ const collectConfigurations = ( traverseWithContext(program, context, (node, parent) => { const details = configurationDetails(node, parent, context); if (details === null) return; - if ( - limitReached( - output.length, - context.state.limits.maxConfigurationOperations, - "maxConfigurationOperations", - context.state, - ) - ) - return; const operation: JavaScriptSemanticConfigurationOperation = { configId: `config:${details.kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: details.kind, @@ -178,15 +168,6 @@ const collectDefaults = ( containsNode(node.left, source), ); if (sources.length !== 1 || sources[0] === undefined) return; - if ( - limitReached( - configurations.length + output.length, - context.state.limits.maxConfigurationOperations, - "maxConfigurationOperations", - context.state, - ) - ) - return; output.push({ configId: `config:default:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: "default", @@ -216,15 +197,6 @@ const collectRequests = ( return; const method = requestMethod(node, context.state); if (method === null) return; - if ( - limitReached( - output.length, - context.state.limits.maxRequestOperations, - "maxRequestOperations", - context.state, - ) - ) - return; const endpoint = requestEndpoint(node); output.push({ node, @@ -276,15 +248,6 @@ const collectResponseConsumers = ( ? [] : (requestsByBinding.get(binding.bindingId) ?? []); if (linked.length === 0) return; - if ( - limitReached( - requests.length + output.length, - context.state.limits.maxRequestOperations, - "maxRequestOperations", - context.state, - ) - ) - return; output.push({ requestId: `response:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: "response-consumer", @@ -312,15 +275,6 @@ const collectBoundaries = ( if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node)) return; const details = boundaryDetails(node, context.state); if (details === null) return; - if ( - limitReached( - output.length, - context.state.limits.maxBoundaryOperations, - "maxBoundaryOperations", - context.state, - ) - ) - return; output.push({ boundaryId: `boundary:${details.kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: details.kind, diff --git a/src/domain/javascriptSemanticGraphQuery.test.ts b/src/domain/javascriptSemanticGraphQuery.test.ts index b0d3a3c4fa..f6bd646f72 100644 --- a/src/domain/javascriptSemanticGraphQuery.test.ts +++ b/src/domain/javascriptSemanticGraphQuery.test.ts @@ -181,7 +181,7 @@ const fixtureGraph = (withUnknown = false): JavaScriptSemanticGraph => { ); const fingerprint = createJavaScriptSemanticFingerprint({ function_node_id: callable.node_id, - algorithm: "rea.javascript-semantic-function/v1", + algorithm: "rea.javascript-semantic-function", status: "complete", components: { parameter_arity: 0, diff --git a/src/domain/javascriptSemanticGraphSchemas.ts b/src/domain/javascriptSemanticGraphSchemas.ts index d728f8d41a..c90fb3f87f 100644 --- a/src/domain/javascriptSemanticGraphSchemas.ts +++ b/src/domain/javascriptSemanticGraphSchemas.ts @@ -194,11 +194,6 @@ const sourceRangeSchema = z const semanticPropertiesSchema = z .record(z.string().min(1).max(128), jsonValueSchema) .superRefine((properties, context) => { - if (Object.keys(properties).length > 64) - context.addIssue({ - code: "custom", - message: "Semantic properties exceed 64 keys", - }); if ( !isJsonWithinLimits(properties, { maxDepth: 6, @@ -313,7 +308,7 @@ const fingerprintComponentsSchema = z.strictObject({ /** One bounded rename-resistant function fingerprint. */ export const javaScriptSemanticFingerprintInputSchema = z.strictObject({ function_node_id: semanticNodeIdSchema, - algorithm: z.literal("rea.javascript-semantic-function/v1"), + algorithm: z.literal("rea.javascript-semantic-function"), status: z.enum(["complete", "partial", "unavailable"]), components: fingerprintComponentsSchema, limitations: z.array(boundedTextSchema).max(100), @@ -340,15 +335,13 @@ const graphCoverageSchema = z.strictObject({ truncated: z.boolean(), omitted_nodes: z.number().int().min(0).nullable(), omitted_relations: z.number().int().min(0).nullable(), - limits: z - .array( - z.strictObject({ - name: z.string().min(1).max(128), - value: z.number().int().min(0), - unit: z.enum(["items", "bytes", "milliseconds", "depth", "other"]), - }), - ) - .max(32), + limits: z.array( + z.strictObject({ + name: z.string().min(1).max(128), + value: z.number().int().min(0), + unit: z.enum(["items", "bytes", "milliseconds", "depth", "other"]), + }), + ), families: z.array(familyCoverageSchema).length(12), }); @@ -363,7 +356,7 @@ export const javaScriptSemanticGraphInputSchema = z.strictObject({ fingerprints: z.array(javaScriptSemanticFingerprintSchema), unknowns: z.array(javaScriptSemanticUnknownSchema), coverage: graphCoverageSchema, - limitations: z.array(boundedTextSchema).max(1_000), + limitations: z.array(boundedTextSchema), }); /** Stored JavaScript Semantic Relation Graph. */ diff --git a/src/domain/javascriptSemanticGraphTraversal.test.ts b/src/domain/javascriptSemanticGraphTraversal.test.ts index 54401a6f3b..4f7ba18b23 100644 --- a/src/domain/javascriptSemanticGraphTraversal.test.ts +++ b/src/domain/javascriptSemanticGraphTraversal.test.ts @@ -177,7 +177,7 @@ const fixtureGraph = (withUnknown = false): JavaScriptSemanticGraph => { ); const fingerprint = createJavaScriptSemanticFingerprint({ function_node_id: callable.node_id, - algorithm: "rea.javascript-semantic-function/v1", + algorithm: "rea.javascript-semantic-function", status: "complete", components: { parameter_arity: 0, diff --git a/src/domain/javascriptSemanticIr.ts b/src/domain/javascriptSemanticIr.ts index 5dfdea895d..7a21ce7ab8 100644 --- a/src/domain/javascriptSemanticIr.ts +++ b/src/domain/javascriptSemanticIr.ts @@ -13,58 +13,12 @@ export type { /** Hard bounds for one execution-free JavaScript semantic analysis. */ export interface JavaScriptSemanticLimits { - readonly maxScopes: number; - readonly maxBindings: number; - readonly maxCallables: number; - readonly maxReferences: number; - readonly maxModuleLinks: number; - readonly maxReturnSites: number; - readonly maxCallSites: number; - readonly maxCallArguments: number; - readonly maxArgumentFlows: number; - readonly maxCallReturnFlows: number; - readonly maxClosureCaptures: number; - readonly maxPromiseOperations: number; - readonly maxEventOperations: number; - readonly maxTimerOperations: number; - readonly maxChildProcessOperations: number; - readonly maxConfigurationOperations: number; - readonly maxRequestOperations: number; - readonly maxBoundaryOperations: number; - readonly maxResourceOperations: number; - readonly maxObjectOperations: number; - readonly maxFrontiers: number; readonly maxValueDepth: number; - readonly maxUnionValues: number; - readonly maxObjectProperties: number; } -/** Conservative defaults for one source file. */ +/** Stack guard for recursive value and provenance recovery. */ export const DEFAULT_JAVASCRIPT_SEMANTIC_LIMITS: JavaScriptSemanticLimits = { - maxScopes: 4_096, - maxBindings: 20_000, - maxCallables: 20_000, - maxReferences: 100_000, - maxModuleLinks: 20_000, - maxReturnSites: 20_000, - maxCallSites: 20_000, - maxCallArguments: 100_000, - maxArgumentFlows: 100_000, - maxCallReturnFlows: 100_000, - maxClosureCaptures: 100_000, - maxPromiseOperations: 100_000, - maxEventOperations: 100_000, - maxTimerOperations: 100_000, - maxChildProcessOperations: 100_000, - maxConfigurationOperations: 100_000, - maxRequestOperations: 100_000, - maxBoundaryOperations: 100_000, - maxResourceOperations: 100_000, - maxObjectOperations: 100_000, - maxFrontiers: 20_000, maxValueDepth: 16, - maxUnionValues: 32, - maxObjectProperties: 256, }; /** One exact module origin followed through imports, requires, or aliases. */ @@ -211,7 +165,7 @@ export interface JavaScriptSemanticClosureCapture { readonly referenceLocation: JavaScriptSourceRange; } -/** One bounded promise/task operation recovered from explicit syntax. */ +/** One promise/task operation recovered from explicit syntax. */ export interface JavaScriptSemanticPromiseOperation { readonly promiseId: string; readonly kind: @@ -293,7 +247,7 @@ export interface JavaScriptSemanticEventOperation { readonly resolution: "complete" | "partial" | "unresolved"; } -/** One bounded timer scheduling or cancellation candidate. */ +/** One timer scheduling or cancellation candidate. */ export interface JavaScriptSemanticTimerOperation { readonly timerId: string; readonly kind: "schedule" | "cancel"; diff --git a/src/domain/javascriptSemanticObjects.ts b/src/domain/javascriptSemanticObjects.ts index 2058ea9761..bc593f6d24 100644 --- a/src/domain/javascriptSemanticObjects.ts +++ b/src/domain/javascriptSemanticObjects.ts @@ -5,7 +5,6 @@ import type { JavaScriptSemanticObjectOperation, } from "./javascriptSemanticIr.js"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -140,10 +139,6 @@ const addObjectOperation = ( state: JavaScriptSemanticAnalysisState, output: JavaScriptSemanticObjectOperation[], ): void => { - if (output.length >= state.limits.maxObjectOperations) { - reachSemanticLimit(state, "maxObjectOperations"); - return; - } output.push({ objectOperationId: `object:${input.kind}:${String(input.node.start ?? -1)}:${String(input.node.end ?? -1)}`, kind: input.kind, diff --git a/src/domain/javascriptSemanticPrimitives.ts b/src/domain/javascriptSemanticPrimitives.ts index 3c1e6d01fb..e8c139dddb 100644 --- a/src/domain/javascriptSemanticPrimitives.ts +++ b/src/domain/javascriptSemanticPrimitives.ts @@ -4,17 +4,12 @@ import type { JavaScriptSemanticPrimitive, JavaScriptSemanticValue, } from "./javascriptSemanticIr.js"; -import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js"; -import { - reachSemanticValueLimit, - semanticPrimitiveKey, -} from "./javascriptSemanticProvenance.js"; +import { semanticPrimitiveKey } from "./javascriptSemanticProvenance.js"; import { compareCodePoints } from "./javascriptStaticAnalysisHelpers.js"; /** Normalize one bounded collection of possible primitive values. */ export const semanticPrimitiveSet = ( values: readonly JavaScriptSemanticPrimitive[], - state: JavaScriptSemanticAnalysisState, ): JavaScriptSemanticValue => { const unique = [ ...new Map( @@ -23,13 +18,6 @@ export const semanticPrimitiveSet = ( ].sort((left, right) => compareCodePoints(semanticPrimitiveKey(left), semanticPrimitiveKey(right)), ); - if (unique.length > state.limits.maxUnionValues) { - reachSemanticValueLimit(state, "maxUnionValues"); - return { - status: "limit-reached", - reason: "maxUnionValues reached.", - }; - } const only = unique[0]; return unique.length === 1 && only !== undefined ? { status: "literal", value: only } diff --git a/src/domain/javascriptSemanticProjection.ts b/src/domain/javascriptSemanticProjection.ts index c13bfe9544..aa7533c8b9 100644 --- a/src/domain/javascriptSemanticProjection.ts +++ b/src/domain/javascriptSemanticProjection.ts @@ -38,10 +38,6 @@ export const collectSemanticCallable = (input: CollectCallableInput): void => { const { node, parent, containerScope, bodyScope, state } = input; const kind = callableKind(node); if (kind === undefined) return; - if (state.callables.length >= state.limits.maxCallables) { - reachSemanticLimit(state, "maxCallables"); - return; - } const callableId = semanticCallableIdForNode(node) ?? "callable:unknown:-1:-1"; state.callables.push({ @@ -110,10 +106,6 @@ export const collectSemanticReferences = ( const key = `${String(node.start)}:${role}:${node.name}`; if (seen.has(key)) return; seen.add(key); - if (output.length >= state.limits.maxReferences) { - reachSemanticLimit(state, "maxReferences"); - return; - } const binding = resolveSemanticBindingState(state, node, node.name); const blocked = binding === undefined && @@ -372,10 +364,6 @@ const addModuleLink = ( readonly callableId?: string | null; }, ): void => { - if (state.moduleLinks.length >= state.limits.maxModuleLinks) { - reachSemanticLimit(state, "maxModuleLinks"); - return; - } state.moduleLinks.push({ ...link, callableId: link.callableId ?? null }); }; diff --git a/src/domain/javascriptSemanticPromises.ts b/src/domain/javascriptSemanticPromises.ts index fdb34c0962..1d1f93c231 100644 --- a/src/domain/javascriptSemanticPromises.ts +++ b/src/domain/javascriptSemanticPromises.ts @@ -5,7 +5,6 @@ import type { JavaScriptSemanticPromiseOperation, } from "./javascriptSemanticIr.js"; import { - reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js"; @@ -110,16 +109,13 @@ const collectCandidates = ( callableStack.push(callableId); const details = promiseCandidateDetails(node, state); if (details !== null) { - if (output.length >= state.limits.maxPromiseOperations) - reachSemanticLimit(state, "maxPromiseOperations"); - else - output.push({ - node, - ...details, - promiseId: semanticPromiseId(details.kind, node), - ownerCallableId: callableStack.at(-1) ?? null, - ancestors: [...ancestors], - }); + output.push({ + node, + ...details, + promiseId: semanticPromiseId(details.kind, node), + ownerCallableId: callableStack.at(-1) ?? null, + ancestors: [...ancestors], + }); } ancestors.push(node); }, diff --git a/src/domain/javascriptSemanticProvenance.ts b/src/domain/javascriptSemanticProvenance.ts index 360599bf34..1f92f8430d 100644 --- a/src/domain/javascriptSemanticProvenance.ts +++ b/src/domain/javascriptSemanticProvenance.ts @@ -19,20 +19,17 @@ export const semanticUnresolvedProvenance = ( reason: string, ): JavaScriptBindingProvenance => ({ status, origins: [], reason }); -/** Construct ambiguous provenance while retaining every bounded origin. */ +/** Construct ambiguous provenance while retaining every origin. */ export const semanticAmbiguousProvenance = ( origins: readonly JavaScriptModuleOrigin[], reason: string, ): JavaScriptBindingProvenance => ({ status: "ambiguous", origins, reason }); -/** Normalize bounded exact module origins into one provenance classification. */ +/** Normalize exact module origins into one provenance classification. */ export const semanticOriginsProvenance = ( origins: readonly JavaScriptModuleOrigin[], - state: JavaScriptSemanticAnalysisState, ): JavaScriptBindingProvenance => { const unique = uniqueSemanticOrigins(origins); - if (unique.length > state.limits.maxUnionValues) - return semanticLimitProvenance(state, "maxUnionValues"); const origin = unique[0]; return unique.length === 1 && origin !== undefined ? { status: "module", origins: [origin], reason: null } @@ -54,7 +51,7 @@ export const uniqueSemanticOrigins = ( /** Mark and return an explicit provenance-limit value. */ export const semanticLimitProvenance = ( state: JavaScriptSemanticAnalysisState, - limit: "maxValueDepth" | "maxUnionValues", + limit: "maxValueDepth", ): JavaScriptBindingProvenance => { reachSemanticValueLimit(state, limit); return semanticUnresolvedProvenance("limit-reached", `${limit} reached.`); diff --git a/src/domain/javascriptSemanticResources.ts b/src/domain/javascriptSemanticResources.ts index 3400d1784f..b611e65fc1 100644 --- a/src/domain/javascriptSemanticResources.ts +++ b/src/domain/javascriptSemanticResources.ts @@ -11,7 +11,6 @@ import { type JavaScriptSemanticBindingState, } from "./javascriptSemanticState.js"; import { - dataEffectLimitReached, outerDataEffectBinding, traverseDataEffects, type DataEffectTraversalContext, @@ -63,7 +62,6 @@ const collectAcquisitions = ( if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node)) return; const method = acquisitionMethod(node.callee, context.state); if (method === null) return; - if (resourceLimitReached(output.length, context.state)) return; output.push({ node, method, @@ -105,10 +103,6 @@ const collectReleases = ( const linked = binding === undefined ? [] : (byBinding.get(binding.bindingId) ?? []); if (linked.length === 0) return; - if ( - resourceLimitReached(acquisitions.length + output.length, context.state) - ) - return; output.push({ resourceId: `resource:release:${String(node.start ?? -1)}:${String(node.end ?? -1)}`, kind: "release", @@ -193,17 +187,6 @@ const resourceModule = (specifier: string): boolean => "tls", ].includes(specifier); -const resourceLimitReached = ( - retained: number, - state: JavaScriptSemanticAnalysisState, -): boolean => - dataEffectLimitReached( - retained, - state.limits.maxResourceOperations, - "maxResourceOperations", - state, - ); - const resourceCandidateId = (candidate: ResourceCandidate): string => `resource:acquire:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`; diff --git a/src/domain/javascriptSemanticReturns.ts b/src/domain/javascriptSemanticReturns.ts index 0c5bccd96a..4f9688e877 100644 --- a/src/domain/javascriptSemanticReturns.ts +++ b/src/domain/javascriptSemanticReturns.ts @@ -10,10 +10,7 @@ import type { ProjectedPropertyCoverage, ProjectedReturnField, } from "./javascriptExportShapeComparisonSchemas.js"; -import { - reachSemanticLimit, - semanticCallableIdForNode, -} from "./javascriptSemanticProjection.js"; +import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js"; import { resolveSemanticBindingState, type JavaScriptSemanticAnalysisState, @@ -29,14 +26,13 @@ interface ReturnExpression { readonly location: JavaScriptSemanticReturnSite["location"]; } -/** Evaluate bounded direct returns while excluding every nested callable. */ +/** Evaluate direct returns while excluding every nested callable. */ export const collectSemanticReturns = ( program: t.Program, state: JavaScriptSemanticAnalysisState, parserPartial: boolean, ): JavaScriptSemanticCallable[] => { const nodes = callableNodes(program); - let retained = 0; return state.callables.map((callable) => { const node = nodes.get(callable.callableId); if (node === undefined) @@ -51,14 +47,7 @@ export const collectSemanticReturns = ( }; const expressions = directReturnExpressions(node); const sites: JavaScriptSemanticReturnSite[] = []; - let omitted = 0; for (const expression of expressions) { - if (retained >= state.limits.maxReturnSites) { - omitted += 1; - reachSemanticLimit(state, "maxReturnSites"); - continue; - } - retained += 1; sites.push({ returnSiteId: semanticReturnSiteId( callable.callableId, @@ -78,11 +67,7 @@ export const collectSemanticReturns = ( return { ...callable, returnSites: sites, - returnCoverage: semanticReturnCoverage( - sites.length, - omitted, - parserPartial, - ), + returnCoverage: semanticReturnCoverage(sites.length, parserPartial), }; }); }; diff --git a/src/domain/javascriptSemanticValues.ts b/src/domain/javascriptSemanticValues.ts index eec5cd4133..bbc53d1cea 100644 --- a/src/domain/javascriptSemanticValues.ts +++ b/src/domain/javascriptSemanticValues.ts @@ -102,21 +102,15 @@ const evaluateExpression = ( } if (t.isTemplateLiteral(node)) return evaluateTemplate(node, context); if (t.isConditionalExpression(node)) - return mergeValues( - [ - evaluateExpression(node.consequent, nestedContext(context)), - evaluateExpression(node.alternate, nestedContext(context)), - ], - context.state, - ); + return mergeValues([ + evaluateExpression(node.consequent, nestedContext(context)), + evaluateExpression(node.alternate, nestedContext(context)), + ]); if (t.isLogicalExpression(node)) - return mergeValues( - [ - evaluateExpression(node.left, nestedContext(context)), - evaluateExpression(node.right, nestedContext(context)), - ], - context.state, - ); + return mergeValues([ + evaluateExpression(node.left, nestedContext(context)), + evaluateExpression(node.right, nestedContext(context)), + ]); if (t.isObjectExpression(node)) return evaluateObject(node, context); if (t.isArrayExpression(node)) return evaluateArray(node, context); if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) @@ -156,10 +150,8 @@ const evaluateTemplate = ( candidates = candidates.flatMap((prefix) => values.map((value) => `${prefix}${String(value)}`), ); - if (candidates.length > context.state.limits.maxUnionValues) - return limitValue(context.state, "maxUnionValues"); } - return primitiveSet(candidates, context.state); + return primitiveSet(candidates); }; const evaluateObject = ( @@ -186,12 +178,6 @@ const evaluateObject = ( if (omittedProperties !== null) omittedProperties += 1; continue; } - if (properties.length >= context.state.limits.maxObjectProperties) { - reachSemanticValueLimit(context.state, "maxObjectProperties"); - unknownProperties = true; - if (omittedProperties !== null) omittedProperties += 1; - continue; - } properties.push({ name, value: evaluateExpression(property.value, nestedContext(context)), @@ -231,12 +217,6 @@ const evaluateArray = ( if (omittedItems !== null) omittedItems += 1; continue; } - if (items.length >= context.state.limits.maxObjectProperties) { - reachSemanticValueLimit(context.state, "maxObjectProperties"); - unknownItems = true; - if (omittedItems !== null) omittedItems += 1; - continue; - } items.push(evaluateExpression(element, nestedContext(context))); } return unknownItems @@ -277,7 +257,7 @@ const evaluateAddition = ( : `${String(leftValue)}${String(rightValue)}`, ), ); - return primitiveSet(values, context.state); + return primitiveSet(values); }; const evaluateUnary = ( @@ -290,20 +270,11 @@ const evaluateUnary = ( if (argument === null) return { status: "unknown", reason: "Non-primitive unary operand." }; if (node.operator === "!") - return primitiveSet( - argument.map((value) => !value), - context.state, - ); + return primitiveSet(argument.map((value) => !value)); if (node.operator === "+") - return primitiveSet( - argument.map((value) => Number(value)), - context.state, - ); + return primitiveSet(argument.map((value) => Number(value))); if (node.operator === "-") - return primitiveSet( - argument.map((value) => -Number(value)), - context.state, - ); + return primitiveSet(argument.map((value) => -Number(value))); return { status: "unknown", reason: `Unsupported unary ${node.operator}.` }; }; @@ -355,7 +326,7 @@ const provenanceForBinding = ( if (context.depth >= context.state.limits.maxValueDepth) return semanticLimitProvenance(context.state, "maxValueDepth"); if (binding.directOrigins.length > 0) - return semanticOriginsProvenance(binding.directOrigins, context.state); + return semanticOriginsProvenance(binding.directOrigins); if (binding.initializers.length === 0) return semanticLocalProvenance(); if (binding.initializers.length > 1) return semanticAmbiguousProvenance( @@ -387,7 +358,6 @@ const provenanceForBinding = ( ...initializer.projection.map((segment) => String(segment)), ], })), - context.state, ); }; @@ -396,8 +366,7 @@ const provenanceForExpression = ( context: EvaluationContext, ): JavaScriptBindingProvenance => { const required = requireOrigin(node); - if (required !== undefined) - return semanticOriginsProvenance([required], context.state); + if (required !== undefined) return semanticOriginsProvenance([required]); if (t.isIdentifier(node)) { const binding = resolveSemanticBindingState(context.state, node, node.name); return binding === undefined @@ -427,7 +396,6 @@ const provenanceForExpression = ( ...origin, importedPath: [...origin.importedPath, member], })), - context.state, ); } if (t.isConditionalExpression(node) || t.isLogicalExpression(node)) { @@ -471,7 +439,6 @@ const requireOrigin = (node: t.Node): JavaScriptModuleOrigin | undefined => { const mergeValues = ( values: readonly JavaScriptSemanticValue[], - state: JavaScriptSemanticAnalysisState, ): JavaScriptSemanticValue => { const primitives = values.flatMap( (value) => primitiveCandidates(value) ?? [], @@ -480,7 +447,7 @@ const mergeValues = ( values.every( (value) => value.status === "union" || value.status === "literal", ) - ? primitiveSet(primitives, state) + ? primitiveSet(primitives) : { status: "ambiguous", reason: "Branches have incompatible values." }; }; @@ -495,7 +462,7 @@ const memberKey = ( const limitValue = ( state: JavaScriptSemanticAnalysisState, - limit: "maxValueDepth" | "maxUnionValues", + limit: "maxValueDepth", ): JavaScriptSemanticValue => { reachSemanticValueLimit(state, limit); return { status: "limit-reached", reason: `${limit} reached.` }; diff --git a/src/domain/javascriptStaticAnalysis.no_finding_caps.test.ts b/src/domain/javascriptStaticAnalysis.no_finding_caps.test.ts new file mode 100644 index 0000000000..7237bda236 --- /dev/null +++ b/src/domain/javascriptStaticAnalysis.no_finding_caps.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js"; + +describe("JavaScript static analysis findings", () => { + it("retains every static reference and Electron property in large literals", () => { + const names = Array.from({ length: 300 }, (_, index) => `key${index}`); + const source = [ + ...names.map( + (name, index) => `import value${index} from "./${name}.js";`, + ), + `new BrowserWindow({ webPreferences: { ${names.map((name) => `${name}: true`).join(", ")} } });`, + `contextBridge.exposeInMainWorld("api", { ${names.map((name) => `${name}: () => true`).join(", ")} });`, + `const { ${names.join(", ")} } = require("./addon.node");`, + ].join("\n"); + const analysis = analyzeJavaScriptStaticSource(source, { + maxAstNodes: 100_000, + deadline: Number.POSITIVE_INFINITY, + now: () => 0, + }); + + expect(analysis.parse_status).toBe("complete"); + expect(analysis.references).toHaveLength(301); + expect(analysis.electron.browser_windows[0]?.web_preferences).toHaveLength( + 300, + ); + expect(analysis.electron.context_bridge_apis[0]?.members).toHaveLength(300); + expect(analysis.electron.native_addon_bindings[0]?.members).toHaveLength( + 300, + ); + }); +}); diff --git a/src/domain/javascriptStaticAnalysis.ts b/src/domain/javascriptStaticAnalysis.ts index 671b264e2a..9e925dfad4 100644 --- a/src/domain/javascriptStaticAnalysis.ts +++ b/src/domain/javascriptStaticAnalysis.ts @@ -52,7 +52,7 @@ export const analyzeParsedJavaScriptStaticSource = ( ): JavaScriptStaticAnalysis => { const accumulator = createJavaScriptAnalysisAccumulator(); traverseStaticSource(source, file, accumulator, limits); - addSourceMapDirectives(source, accumulator, limits.maxFindings); + addSourceMapDirectives(source, accumulator); return finalizeStaticAnalysis(source, file, accumulator, limits); }; @@ -95,12 +95,6 @@ const finalizeStaticAnalysis = ( ...(accumulator.truncated ? ["AST analysis stopped at the configured node or time bound."] : []), - ...(accumulator.droppedFindings === 0 - ? [] - : ["Static findings were truncated at the configured finding bound."]), - ...(accumulator.structuralTruncation - ? ["Bundler metadata reached a per-record structural bound."] - : []), ...(accumulator.unknownFindings === 0 ? [] : [ @@ -109,15 +103,13 @@ const finalizeStaticAnalysis = ( "JavaScript syntax was parsed as data and was never evaluated.", ]; return { - parse_status: - accumulator.truncated || accumulator.droppedFindings > 0 - ? "truncated" - : parserErrors > 0 || accumulator.unknownFindings > 0 - ? "partial" - : "complete", + parse_status: accumulator.truncated + ? "truncated" + : parserErrors > 0 || accumulator.unknownFindings > 0 + ? "partial" + : "complete", parse_error_count: parserErrors, visited_ast_nodes: Math.min(accumulator.visitedNodes, limits.maxAstNodes), - dropped_findings: accumulator.droppedFindings, references: finalizeLocatedFindings( accumulator.references, accumulator.modules, @@ -170,7 +162,6 @@ const inspectNode = ( const findings = { source, accumulator, - maximum: limits.maxFindings, }; inspectElectronStaticNode(node, findings); if (t.isCallExpression(node)) { diff --git a/src/domain/javascriptStaticAnalysisBundler.ts b/src/domain/javascriptStaticAnalysisBundler.ts index 74003f35c0..2b26dfaf86 100644 --- a/src/domain/javascriptStaticAnalysisBundler.ts +++ b/src/domain/javascriptStaticAnalysisBundler.ts @@ -4,7 +4,7 @@ import { collectJavaScriptExports, fingerprintJavaScriptAst, } from "./javascriptAstFingerprint.js"; -import { addBoundedFinding } from "./javascriptStaticAnalysisFindings.js"; +import { addFindingOnce } from "./javascriptStaticAnalysisFindings.js"; import { argumentValue, calleeName, @@ -38,59 +38,31 @@ export const inspectBundlerRegistration = ( if (runtime === undefined || !t.isArrayExpression(entry)) return; const chunkIds = entry.elements[0]; const table = entry.elements[1]; - const runtimeValue = runtimeMetadata(entry.elements[2], limits.maxFindings); + const runtimeValue = runtimeMetadata(entry.elements[2]); if (!t.isArrayExpression(chunkIds) || !t.isObjectExpression(table)) return; const recovered = recoverBundlerModules(source, table, accumulator, limits); - const chunkKeys = staticArrayValues( - chunkIds, - Math.min(256, limits.maxFindings), - ); - if (chunkKeys.omitted > 0) { - accumulator.droppedFindings += chunkKeys.omitted; - accumulator.structuralTruncation = true; - } + const chunkKeys = staticArrayValues(chunkIds); accumulator.unknownFindings += chunkKeys.unknown; - if ( - runtimeValue.omittedEntryModuleKeys > 0 || - runtimeValue.omittedAsyncChunkKeys > 0 || - recovered.omittedAsyncChunkKeys > 0 - ) { - accumulator.droppedFindings += - runtimeValue.omittedEntryModuleKeys + - runtimeValue.omittedAsyncChunkKeys + - recovered.omittedAsyncChunkKeys; - accumulator.structuralTruncation = true; - } accumulator.unknownFindings += runtimeValue.unknownEntryModuleKeys + runtimeValue.unknownAsyncChunkKeys + recovered.unknownAsyncChunkKeys; - const asyncChunkKeys = boundedUniqueValues( - [...runtimeValue.asyncChunkKeys, ...recovered.asyncChunkKeys], - Math.min(256, limits.maxFindings), - ); - if (asyncChunkKeys.omitted > 0) { - accumulator.droppedFindings += asyncChunkKeys.omitted; - accumulator.structuralTruncation = true; - } + const asyncChunkKeys = uniqueValues([ + ...runtimeValue.asyncChunkKeys, + ...recovered.asyncChunkKeys, + ]); const registration = { bundler: runtime.toLowerCase().includes("rspack") ? ("rspack" as const) : ("webpack" as const), runtime, chunk_keys: chunkKeys.values, - omitted_chunk_keys: chunkKeys.omitted, unknown_chunk_keys: chunkKeys.unknown, runtime_require_name: runtimeValue.requireName, runtime_module_cache_status: bundlerModuleCacheStatus(source), entry_module_keys: runtimeValue.entryModuleKeys, - omitted_entry_module_keys: runtimeValue.omittedEntryModuleKeys, unknown_entry_module_keys: runtimeValue.unknownEntryModuleKeys, async_chunk_keys: asyncChunkKeys.values, - omitted_async_chunk_keys: - runtimeValue.omittedAsyncChunkKeys + - recovered.omittedAsyncChunkKeys + - asyncChunkKeys.omitted, unknown_async_chunk_keys: runtimeValue.unknownAsyncChunkKeys + recovered.unknownAsyncChunkKeys, modules: recovered.modules.sort((left, right) => @@ -98,10 +70,9 @@ export const inspectBundlerRegistration = ( ), location: range(call), }; - addBoundedFinding( + addFindingOnce( accumulator, `registration\0${registrationKey(registration)}`, - limits.maxFindings, () => accumulator.registrations.push(registration), ); }; @@ -124,34 +95,26 @@ export const inspectEsbuildWrapper = ( if (wrapperKind === null || !t.isObjectExpression(table)) return; const recovered = recoverBundlerModules(source, table, accumulator, limits); if (recovered.modules.length === 0) return; - if (recovered.omittedAsyncChunkKeys > 0) { - accumulator.droppedFindings += recovered.omittedAsyncChunkKeys; - accumulator.structuralTruncation = true; - } accumulator.unknownFindings += recovered.unknownAsyncChunkKeys; const registration: JavaScriptBundlerRegistration = { bundler: "esbuild", runtime: `esbuild-${wrapperKind}`, chunk_keys: [`${wrapperKind}@${String(call.start ?? 0)}`], - omitted_chunk_keys: 0, unknown_chunk_keys: 0, runtime_require_name: null, runtime_module_cache_status: "not-observed", entry_module_keys: [], - omitted_entry_module_keys: 0, unknown_entry_module_keys: 0, async_chunk_keys: recovered.asyncChunkKeys, - omitted_async_chunk_keys: recovered.omittedAsyncChunkKeys, unknown_async_chunk_keys: recovered.unknownAsyncChunkKeys, modules: recovered.modules.sort((left, right) => compareCodePoints(left.module_key, right.module_key), ), location: range(call), }; - addBoundedFinding( + addFindingOnce( accumulator, `registration\0${registrationKey(registration)}`, - limits.maxFindings, () => accumulator.registrations.push(registration), ); }; @@ -159,7 +122,6 @@ export const inspectEsbuildWrapper = ( interface RecoveredBundlerModules { readonly modules: JavaScriptBundlerModule[]; readonly asyncChunkKeys: readonly string[]; - readonly omittedAsyncChunkKeys: number; readonly unknownAsyncChunkKeys: number; } @@ -171,7 +133,6 @@ const recoverBundlerModules = ( ): RecoveredBundlerModules => { const modules: JavaScriptBundlerModule[] = []; const asyncChunkKeys: string[] = []; - let omittedAsyncChunkKeys = 0; let unknownAsyncChunkKeys = 0; for (const property of table.properties) { const recovered = recoverBundlerModule( @@ -183,13 +144,11 @@ const recoverBundlerModules = ( if (recovered === null) continue; modules.push(recovered.module); asyncChunkKeys.push(...recovered.asyncChunkKeys); - omittedAsyncChunkKeys += recovered.omittedAsyncChunkKeys; unknownAsyncChunkKeys += recovered.unknownAsyncChunkKeys; } return { modules, asyncChunkKeys, - omittedAsyncChunkKeys, unknownAsyncChunkKeys, }; }; @@ -202,34 +161,17 @@ const recoverBundlerModule = ( ): { readonly module: JavaScriptBundlerModule; readonly asyncChunkKeys: readonly string[]; - readonly omittedAsyncChunkKeys: number; readonly unknownAsyncChunkKeys: number; } | null => { const factory = moduleFactory(property); if (factory === undefined) return null; - if (accumulator.moduleCount >= limits.maxModules) { - accumulator.droppedFindings += 1; - return null; - } - accumulator.moduleCount += 1; const key = modulePropertyName(property); if (key.startsWith("[computed@") || key.startsWith("[unknown@")) accumulator.unknownFindings += 1; - const fingerprint = fingerprintJavaScriptAst( - factory, - Math.min(limits.maxAstNodes, 100_000), - ); - const exportsValue = collectJavaScriptExports(factory, 256); + const fingerprint = fingerprintJavaScriptAst(factory, limits.maxAstNodes); + const exportsValue = collectJavaScriptExports(factory); const requireName = factoryRequireName(factory); - const asyncChunks = collectBundlerAsyncChunkKeys( - factory, - requireName, - limits.maxFindings, - ); - if (fingerprint.truncated || exportsValue.truncated) { - accumulator.droppedFindings += 1; - accumulator.structuralTruncation = true; - } + const asyncChunks = collectBundlerAsyncChunkKeys(factory, requireName); if (typeof factory.start === "number" && typeof factory.end === "number") accumulator.modules.push({ start: factory.start, @@ -255,11 +197,9 @@ const recoverBundlerModule = ( source_sha256: sha256Text(sourceSlice(source, factory)), ...structuralFingerprint, exports: exportsValue.values, - exports_truncated: exportsValue.truncated, location: range(factory), }, asyncChunkKeys: asyncChunks.values, - omittedAsyncChunkKeys: asyncChunks.omitted, unknownAsyncChunkKeys: asyncChunks.unknown, }; }; @@ -267,16 +207,13 @@ const recoverBundlerModule = ( interface RuntimeMetadata { readonly requireName: string | null; readonly entryModuleKeys: readonly string[]; - readonly omittedEntryModuleKeys: number; readonly unknownEntryModuleKeys: number; readonly asyncChunkKeys: readonly string[]; - readonly omittedAsyncChunkKeys: number; readonly unknownAsyncChunkKeys: number; } -interface BoundedValues { +interface StaticValues { readonly values: readonly string[]; - readonly omitted: number; readonly unknown: number; } @@ -285,43 +222,32 @@ type BundlerFunction = | t.ArrowFunctionExpression | t.ObjectMethod; -const runtimeMetadata = ( - node: t.Node | null | undefined, - maximum: number, -): RuntimeMetadata => { +const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => { if (!isBundlerFunction(node)) return { requireName: null, entryModuleKeys: [], - omittedEntryModuleKeys: 0, unknownEntryModuleKeys: node === null || node === undefined ? 0 : 1, asyncChunkKeys: [], - omittedAsyncChunkKeys: 0, unknownAsyncChunkKeys: 0, }; const parameter = node.params[0]; - const requireName = t.isIdentifier(parameter) - ? parameter.name.slice(0, 4_096) - : null; + const requireName = t.isIdentifier(parameter) ? parameter.name : null; if (requireName === null) return { requireName, entryModuleKeys: [], - omittedEntryModuleKeys: 0, unknownEntryModuleKeys: 1, asyncChunkKeys: [], - omittedAsyncChunkKeys: 0, unknownAsyncChunkKeys: 0, }; - const entries = collectBundlerEntryModuleKeys(node, requireName, maximum); - const asyncChunks = collectBundlerAsyncChunkKeys(node, requireName, maximum); + const entries = collectBundlerEntryModuleKeys(node, requireName); + const asyncChunks = collectBundlerAsyncChunkKeys(node, requireName); return { requireName, entryModuleKeys: entries.values, - omittedEntryModuleKeys: entries.omitted, unknownEntryModuleKeys: entries.unknown, asyncChunkKeys: asyncChunks.values, - omittedAsyncChunkKeys: asyncChunks.omitted, unknownAsyncChunkKeys: asyncChunks.unknown, }; }; @@ -329,24 +255,20 @@ const runtimeMetadata = ( const collectBundlerEntryModuleKeys = ( factory: BundlerFunction, requireName: string, - maximum: number, -): BoundedValues => - collectBundlerCallArgumentValues(factory, requireName, maximum); +): StaticValues => collectBundlerCallArgumentValues(factory, requireName); const collectBundlerAsyncChunkKeys = ( factory: BundlerFunction, requireName: string | null, - maximum: number, -): BoundedValues => +): StaticValues => requireName === null - ? { values: [], omitted: 0, unknown: 0 } - : collectBundlerCallArgumentValues(factory, `${requireName}.e`, maximum); + ? { values: [], unknown: 0 } + : collectBundlerCallArgumentValues(factory, `${requireName}.e`); const collectBundlerCallArgumentValues = ( factory: BundlerFunction, callee: string, - maximum: number, -): BoundedValues => { +): StaticValues => { const values: string[] = []; let unknown = 0; t.traverseFast(factory, (node) => { @@ -357,7 +279,7 @@ const collectBundlerCallArgumentValues = ( else values.push(value); return undefined; }); - return boundedUniqueValues(values, Math.min(256, maximum), unknown); + return uniqueValues(values, unknown); }; const isBundlerFunction = ( @@ -367,18 +289,10 @@ const isBundlerFunction = ( t.isArrowFunctionExpression(node) || t.isObjectMethod(node); -const boundedUniqueValues = ( - values: readonly string[], - maximum: number, - unknown = 0, -): BoundedValues => { - const bounded = [ - ...new Set(values.map((value) => value.slice(0, 4_096))), - ].sort(compareCodePoints); - const retained = bounded.slice(0, Math.max(0, maximum)); +const uniqueValues = (values: readonly string[], unknown = 0): StaticValues => { + const unique = [...new Set(values)].sort(compareCodePoints); return { - values: retained, - omitted: Math.max(0, bounded.length - retained.length), + values: unique, unknown, }; }; diff --git a/src/domain/javascriptStaticAnalysisCalls.ts b/src/domain/javascriptStaticAnalysisCalls.ts index 9d28102e11..44c81c999b 100644 --- a/src/domain/javascriptStaticAnalysisCalls.ts +++ b/src/domain/javascriptStaticAnalysisCalls.ts @@ -2,7 +2,7 @@ import * as t from "@babel/types"; import { sanitizeEndpointCandidate } from "./browserObservation.js"; import { - addBoundedFinding, + addFindingOnce, addLocatedFinding, moduleAtOffset, } from "./javascriptStaticAnalysisFindings.js"; @@ -143,9 +143,7 @@ const vitePreloadDependencyTable = (source: string): readonly string[] => { if (body === undefined) return []; return [ ...body.matchAll(/"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'/gu), - ] - .map((literal) => (literal[1] ?? literal[2] ?? "").slice(0, 4_096)) - .slice(0, 256); + ].map((literal) => literal[1] ?? literal[2] ?? ""); }; const inspectEndpointCall = ( @@ -236,14 +234,14 @@ export const addReference = ( context: FindingContext, input: ReferenceInput, ): void => { - const boundedSpecifier = input.specifier?.slice(0, 4_096) ?? null; + const specifier = input.specifier ?? null; const reference = - boundedSpecifier === null + specifier === null ? { specifier: null, - expression: sourceSlice(context.source, input.node).slice(0, 4_096), + expression: sourceSlice(context.source, input.node), } - : { specifier: boundedSpecifier, expression: null }; + : { specifier, expression: null }; addLocatedFinding(context, { collection: context.accumulator.references, key: `reference\0${input.kind}\0${reference.specifier ?? reference.expression}`, @@ -274,16 +272,15 @@ const addEndpoint = (context: FindingContext, input: EndpointInput): void => export const addSourceMapDirectives = ( source: string, accumulator: AnalysisAccumulator, - maximum: number, ): void => { for (const match of source.matchAll( /\/\/[#@]\s*sourceMappingURL\s*=\s*([^\s]+)/gu, )) { - const declared = match[1]?.slice(0, 4_096); + const declared = match[1]; if (declared === undefined) continue; const start = match.index; const location = rangeForOffsets(source, start, start + match[0].length); - addBoundedFinding(accumulator, `source-map\0${declared}`, maximum, () => + addFindingOnce(accumulator, `source-map\0${declared}`, () => accumulator.sourceMaps.push({ declared_url: declared, location }), ); } diff --git a/src/domain/javascriptStaticAnalysisFindings.ts b/src/domain/javascriptStaticAnalysisFindings.ts index e272f2a964..0def3a5bd4 100644 --- a/src/domain/javascriptStaticAnalysisFindings.ts +++ b/src/domain/javascriptStaticAnalysisFindings.ts @@ -6,17 +6,16 @@ import type { LocatedJavaScriptFindingInput, } from "./javascriptStaticAnalysisState.js"; -/** Add one module-attributable finding under the shared global finding bound. */ +/** Add one module-attributable finding once per source location. */ export const addLocatedFinding = < Value extends { readonly module_key: string | null }, >( context: JavaScriptFindingContext, input: LocatedJavaScriptFindingInput, ): void => - addBoundedFinding( + addFindingOnce( context.accumulator, `${input.key}\0${String(input.node.start)}`, - context.maximum, () => input.collection.push({ offset: input.node.start ?? -1, @@ -25,17 +24,12 @@ export const addLocatedFinding = < ); /** Reserve one deterministic finding key before mutating an accumulator. */ -export const addBoundedFinding = ( +export const addFindingOnce = ( accumulator: JavaScriptAnalysisAccumulator, key: string, - maximum: number, add: () => void, ): void => { if (accumulator.seen.has(key)) return; - if (accumulatedFindingCount(accumulator) >= maximum) { - accumulator.droppedFindings += 1; - return; - } accumulator.seen.add(key); add(); }; @@ -60,19 +54,3 @@ export const moduleAtOffset = ( if (offset === null || offset === undefined) return undefined; return modules.find(({ start, end }) => offset >= start && offset <= end); }; - -const accumulatedFindingCount = ( - accumulator: JavaScriptAnalysisAccumulator, -): number => - accumulator.references.length + - accumulator.endpoints.length + - accumulator.storage.length + - accumulator.roles.length + - accumulator.sourceMaps.length + - accumulator.registrations.length + - accumulator.browserWindows.length + - accumulator.contextBridgeApis.length + - accumulator.ipc.length + - accumulator.senderValidations.length + - accumulator.utilityProcesses.length + - accumulator.nativeAddonBindings.length; diff --git a/src/domain/javascriptStaticAnalysisHelpers.ts b/src/domain/javascriptStaticAnalysisHelpers.ts index 46299f21d8..4f38b74a03 100644 --- a/src/domain/javascriptStaticAnalysisHelpers.ts +++ b/src/domain/javascriptStaticAnalysisHelpers.ts @@ -16,7 +16,6 @@ export const failedJavaScriptStaticAnalysis = (): JavaScriptStaticAnalysis => ({ parse_status: "failed", parse_error_count: 1, visited_ast_nodes: 0, - dropped_findings: 0, references: [], endpoints: [], storage: [], @@ -50,7 +49,7 @@ export const argumentNode = ( ): t.Node | undefined => argument !== null && t.isNode(argument) ? argument : undefined; -/** Recognize the bounded runtime name for a Webpack/Rspack push call. */ +/** Recognize the runtime name for a Webpack/Rspack push call. */ export const chunkRuntime = (call: t.CallExpression): string | undefined => { if ( !t.isMemberExpression(call.callee) && @@ -64,11 +63,10 @@ export const chunkRuntime = (call: t.CallExpression): string | undefined => { const findChunkRuntime = (node: t.Node, depth: number): string | undefined => { if (depth >= 128) return undefined; if (t.isIdentifier(node) && /(?:webpack|rspack)Chunk/iu.test(node.name)) - return node.name.slice(0, 4_096); + return node.name; if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) { const property = propertyName(node.property); - if (/(?:webpack|rspack)Chunk/iu.test(property)) - return property.slice(0, 4_096); + if (/(?:webpack|rspack)Chunk/iu.test(property)) return property; return t.isNode(node.object) ? findChunkRuntime(node.object, depth + 1) : undefined; @@ -106,7 +104,7 @@ export const moduleFactory = ( return undefined; }; -/** Derive a bounded literal or explicitly computed module key. */ +/** Derive a literal or explicitly computed module key. */ export const modulePropertyName = ( property: t.ObjectMethod | t.ObjectProperty | t.SpreadElement, ): string => @@ -115,7 +113,7 @@ export const modulePropertyName = ( !t.isStringLiteral(property.key) && !t.isNumericLiteral(property.key) ? `[computed@${String(property.start ?? -1)}]` - : propertyName(property.key).slice(0, 4_096) || + : propertyName(property.key) || `[unknown@${String(property.start ?? -1)}]` : `[unknown@${String(property.start ?? -1)}]`; @@ -124,31 +122,28 @@ export const factoryRequireName = ( factory: t.FunctionExpression | t.ArrowFunctionExpression | t.ObjectMethod, ): string | null => { const parameter = factory.params[2]; - return t.isIdentifier(parameter) ? parameter.name.slice(0, 4_096) : null; + return t.isIdentifier(parameter) ? parameter.name : null; }; -/** Collect bounded unique literal values and explicit omissions from an array. */ +/** Collect all unique static literal values from an array. */ export const staticArrayValues = ( array: t.ArrayExpression, - maximum: number, ): { readonly values: readonly string[]; - readonly omitted: number; readonly unknown: number; } => { const staticValues = array.elements.flatMap((element) => { const value = argumentValue(element); - return value === undefined ? [] : [value.slice(0, 4_096)]; + return value === undefined ? [] : [value]; }); const values = [...new Set(staticValues)].sort(compareCodePoints); return { - values: values.slice(0, maximum), - omitted: Math.max(0, values.length - maximum), + values, unknown: array.elements.length - staticValues.length, }; }; -/** Resolve a bounded path composed only from inert literal syntax. */ +/** Resolve a path composed only from inert literal syntax. */ export const staticPath = (node: t.Node): string | undefined => staticPathAt(node, 0); @@ -162,17 +157,17 @@ export const staticPathResolutionContext = ( const staticPathAt = (node: t.Node, depth: number): string | undefined => { if (depth >= 128) return undefined; - if (t.isStringLiteral(node)) return node.value.slice(0, 4_096); + if (t.isStringLiteral(node)) return node.value; if (t.isTemplateLiteral(node) && node.expressions.length === 0) { const value = node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw; - return value?.slice(0, 4_096); + return value; } if (t.isBinaryExpression(node, { operator: "+" })) { const left = staticPathAt(node.left, depth + 1); const right = staticPathAt(node.right, depth + 1); return left === undefined || right === undefined ? undefined - : `${left}${right}`.slice(0, 4_096); + : `${left}${right}`; } if (t.isCallExpression(node) || t.isNewExpression(node)) return staticCallPath(node, depth); @@ -210,7 +205,7 @@ const staticCallPath = ( if (value === undefined) return undefined; parts.push(value); } - return parts.length === 0 ? undefined : posix.join(...parts).slice(0, 4_096); + return parts.length === 0 ? undefined : posix.join(...parts); }; const isFilesystemPathExpression = (node: t.Node, depth: number): boolean => { @@ -310,43 +305,42 @@ export const storageKind = ( return undefined; }; -/** Produce a dotted bounded callee name from member syntax. */ +/** Produce a dotted callee name from member syntax. */ export const calleeName = (node: t.Node): string => calleeNameAt(node, 0); const calleeNameAt = (node: t.Node, depth: number): string => { if (depth >= 128) return "[deep]"; - if (t.isIdentifier(node)) return node.name.slice(0, 4_096); + if (t.isIdentifier(node)) return node.name; if (t.isImport(node)) return "import"; if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) { const object = t.isNode(node.object) ? calleeNameAt(node.object, depth + 1) : ""; const property = propertyName(node.property); - return (object === "" ? property : `${object}.${property}`).slice(0, 4_096); + return object === "" ? property : `${object}.${property}`; } return ""; }; /** Read a literal property name without evaluating computed syntax. */ export const propertyName = (node: t.Node): string => { - if (t.isIdentifier(node)) return node.name.slice(0, 4_096); + if (t.isIdentifier(node)) return node.name; if (t.isStringLiteral(node) || t.isNumericLiteral(node)) - return String(node.value).slice(0, 4_096); + return String(node.value); return ""; }; /** Return a string literal value without evaluating an expression. */ export const stringValue = ( node: t.Node | null | undefined, -): string | undefined => - t.isStringLiteral(node) ? node.value.slice(0, 4_096) : undefined; +): string | undefined => (t.isStringLiteral(node) ? node.value : undefined); /** Return a string or numeric argument literal. */ export const argumentValue = ( node: t.Node | null | undefined, ): string | undefined => { if (t.isStringLiteral(node) || t.isNumericLiteral(node)) - return String(node.value).slice(0, 4_096); + return String(node.value); return undefined; }; @@ -359,7 +353,7 @@ export const prefixedArgument = ( return value === undefined ? undefined : `${prefix}${value}`; }; -/** Retain the exact bounded source span represented by an AST node. */ +/** Retain the exact source span represented by an AST node. */ export const sourceSlice = (source: string, node: t.Node): string => typeof node.start !== "number" || typeof node.end !== "number" ? "" @@ -414,7 +408,7 @@ export const detectVendors = (source: string): string[] => export const registrationKey = ( registration: JavaScriptBundlerRegistration, ): string => - `${registration.runtime}\0${registration.chunk_keys.join("\0")}\0${String(registration.omitted_chunk_keys)}\0${String(registration.unknown_chunk_keys)}\0${registration.modules.map(({ module_key: key, source_sha256: digest }) => `${key}:${digest}`).join("\0")}`; + `${registration.runtime}\0${registration.chunk_keys.join("\0")}\0${String(registration.unknown_chunk_keys)}\0${registration.modules.map(({ module_key: key, source_sha256: digest }) => `${key}:${digest}`).join("\0")}`; /** Sort values by a deterministic unique semantic key. */ export const sortedUnique = ( diff --git a/src/domain/javascriptStaticAnalysisState.ts b/src/domain/javascriptStaticAnalysisState.ts index 532e9712e9..8d2b059e0d 100644 --- a/src/domain/javascriptStaticAnalysisState.ts +++ b/src/domain/javascriptStaticAnalysisState.ts @@ -31,7 +31,7 @@ export interface LocatedJavaScriptFinding { readonly value: Value; } -/** Mutable bounded collections used during one inert AST traversal. */ +/** Mutable collections used during one inert AST traversal. */ export interface JavaScriptAnalysisAccumulator { readonly references: LocatedJavaScriptFinding[]; readonly endpoints: LocatedJavaScriptFinding[]; @@ -48,18 +48,14 @@ export interface JavaScriptAnalysisAccumulator { readonly modules: JavaScriptModuleRange[]; readonly seen: Set; visitedNodes: number; - droppedFindings: number; - moduleCount: number; unknownFindings: number; - structuralTruncation: boolean; truncated: boolean; } -/** Shared source, accumulator, and finding bound for helper inspections. */ +/** Shared source and accumulator for helper inspections. */ export interface JavaScriptFindingContext { readonly source: string; readonly accumulator: JavaScriptAnalysisAccumulator; - readonly maximum: number; } /** Candidate import, require, worker, or service-worker reference. */ @@ -87,7 +83,7 @@ export interface LocatedJavaScriptFindingInput< readonly value: Value; } -/** Create isolated mutable state for one bounded source analysis. */ +/** Create isolated mutable state for one source analysis. */ export const createJavaScriptAnalysisAccumulator = (): JavaScriptAnalysisAccumulator => ({ references: [], @@ -105,9 +101,6 @@ export const createJavaScriptAnalysisAccumulator = modules: [], seen: new Set(), visitedNodes: 0, - droppedFindings: 0, - moduleCount: 0, unknownFindings: 0, - structuralTruncation: false, truncated: false, }); diff --git a/src/domain/javascriptStaticAnalysisTypes.ts b/src/domain/javascriptStaticAnalysisTypes.ts index 81b3ac6a46..6d8fbdc022 100644 --- a/src/domain/javascriptStaticAnalysisTypes.ts +++ b/src/domain/javascriptStaticAnalysisTypes.ts @@ -63,7 +63,6 @@ interface JavaScriptBundlerModuleState { readonly factory_require_name: string | null; readonly source_sha256: string; readonly exports: readonly string[]; - readonly exports_truncated: boolean; readonly location: JavaScriptSourceRange; } @@ -87,15 +86,12 @@ export interface JavaScriptBundlerRegistration { readonly bundler: "webpack" | "rspack" | "esbuild"; readonly runtime: string; readonly chunk_keys: readonly string[]; - readonly omitted_chunk_keys: number; readonly unknown_chunk_keys: number; readonly runtime_require_name: string | null; readonly runtime_module_cache_status: "observed" | "not-observed"; readonly entry_module_keys: readonly string[]; - readonly omitted_entry_module_keys: number; readonly unknown_entry_module_keys: number; readonly async_chunk_keys: readonly string[]; - readonly omitted_async_chunk_keys: number; readonly unknown_async_chunk_keys: number; readonly modules: readonly JavaScriptBundlerModule[]; readonly location: JavaScriptSourceRange; @@ -111,12 +107,11 @@ export interface JavaScriptRolePath { readonly location: JavaScriptSourceRange; } -/** Bounded, deterministic AST-only analysis of one JavaScript source file. */ +/** Deterministic AST-only analysis of one JavaScript source file. */ export interface JavaScriptStaticAnalysis { readonly parse_status: "complete" | "partial" | "failed" | "truncated"; readonly parse_error_count: number; readonly visited_ast_nodes: number; - readonly dropped_findings: number; readonly references: readonly JavaScriptStaticReference[]; readonly endpoints: readonly JavaScriptStaticEndpoint[]; readonly storage: readonly JavaScriptStaticStorage[]; @@ -134,8 +129,6 @@ export interface JavaScriptStaticAnalysis { /** Hard bounds applied to one AST-only JavaScript analysis. */ export interface JavaScriptStaticAnalysisLimits { readonly maxAstNodes: number; - readonly maxFindings: number; - readonly maxModules: number; readonly deadline: number; readonly now: () => number; } diff --git a/src/domain/managedMemberComparison.ts b/src/domain/managedMemberComparison.ts index 506ea80fd7..5fa1b44a8d 100644 --- a/src/domain/managedMemberComparison.ts +++ b/src/domain/managedMemberComparison.ts @@ -150,7 +150,6 @@ export const managedMemberComparisonResultSchema = z.strictObject({ comparison_id: z.string().regex(/^mmc_[a-f0-9]{64}$/u), algorithm: z.strictObject({ name: z.literal("rea-managed-member-comparison"), - version: z.literal(1), token_identity: z.literal("build-local"), name_matching: z.literal("not-used"), }), @@ -247,7 +246,6 @@ export const compareManagedMembers = ( })}`, algorithm: { name: "rea-managed-member-comparison" as const, - version: 1 as const, token_identity: "build-local" as const, name_matching: "not-used" as const, }, diff --git a/src/domain/managedNativeVerificationMatch.ts b/src/domain/managedNativeVerificationMatch.ts index dd3367cc99..478b2fd37d 100644 --- a/src/domain/managedNativeVerificationMatch.ts +++ b/src/domain/managedNativeVerificationMatch.ts @@ -344,7 +344,6 @@ export const buildVerificationResult = ({ return { algorithm: { name: "rea-managed-native-verification" as const, - version: 1 as const, token_identity: "build-local" as const, token_to_address_mapping: "not-inferred" as const, }, diff --git a/src/domain/managedNativeVerificationSchemas.ts b/src/domain/managedNativeVerificationSchemas.ts index 1aac5204cc..5a0736b056 100644 --- a/src/domain/managedNativeVerificationSchemas.ts +++ b/src/domain/managedNativeVerificationSchemas.ts @@ -128,7 +128,6 @@ export const managedNativeVerificationResultSchema = z verification_id: z.string().regex(/^mnv_[a-f0-9]{64}$/u), algorithm: z.strictObject({ name: z.literal("rea-managed-native-verification"), - version: z.literal(1), token_identity: z.literal("build-local"), token_to_address_mapping: z.literal("not-inferred"), }), diff --git a/src/domain/reconstructionCoverage.fixture.ts b/src/domain/reconstructionCoverage.fixture.ts index 84225b5feb..0a51546b64 100644 --- a/src/domain/reconstructionCoverage.fixture.ts +++ b/src/domain/reconstructionCoverage.fixture.ts @@ -12,7 +12,7 @@ const EVIDENCE_RECORDS = ["1", "2", "3", "4", "5"].map((character) => undefined, { id: "fixture", name: "Fixture", version: "1" }, { - predicateType: "rea.coverage-fixture/v1", + predicateType: "rea.coverage-fixture", operation: `fixture-${character}`, parameters: {}, result: { character }, diff --git a/src/domain/reconstructionVerification.test.ts b/src/domain/reconstructionVerification.test.ts index 5e3cda24b6..1c3770b7cc 100644 --- a/src/domain/reconstructionVerification.test.ts +++ b/src/domain/reconstructionVerification.test.ts @@ -34,7 +34,7 @@ const source = ( predicateType: authority === "controlled-replay" ? "rea.process-capture" - : "rea.analysis/v2", + : "rea.analysis", operation: authority === "controlled-replay" ? "capture_process_scenario" @@ -162,7 +162,7 @@ const artifactComparison = ( version: "1", }, { - predicateType: "rea.artifact-comparison/v1", + predicateType: "rea.artifact-comparison", operation: "compare_artifacts", parameters: { left_evidence_ids: [left.evidence_id], @@ -370,7 +370,7 @@ describe("reconstruction verification integrity", () => { undefined, { id: "fixture", name: "Fixture", version: "1" }, { - predicateType: "rea.residual-unknown-mutation/v1", + predicateType: "rea.residual-unknown-mutation", operation: "record_unknown", parameters: { index }, result: { action: "record" }, diff --git a/src/domain/reconstructionVerification.ts b/src/domain/reconstructionVerification.ts index 59e287ea7e..93d181ab1f 100644 --- a/src/domain/reconstructionVerification.ts +++ b/src/domain/reconstructionVerification.ts @@ -52,13 +52,13 @@ const providers = { }, "structural-function": { operation: "compare_functions", - predicates: ["rea.function-comparison/v1"], + predicates: ["rea.function-comparison"], id: "rea-function-comparison", name: "REA function comparison", }, "structural-artifact": { operation: "compare_artifacts", - predicates: ["rea.artifact-comparison/v1"], + predicates: ["rea.artifact-comparison"], id: "rea-artifact-comparison", name: "REA artifact comparison", }, @@ -191,8 +191,8 @@ const validateSourceKinds = ( claim.kind === "behavioral" ? ["capture_process_scenario", "rea.process-capture"] : claim.kind === "structural-function" - ? ["analyze_function", "rea.analysis/v2"] - : ["inventory_artifact", "rea.analysis/v2"]; + ? ["analyze_function", "rea.analysis"] + : ["inventory_artifact", "rea.analysis"]; if ( sources.some( (source) => diff --git a/src/domain/sourceToBundleComparison.test.ts b/src/domain/sourceToBundleComparison.test.ts index a55054020e..bad4e58721 100644 --- a/src/domain/sourceToBundleComparison.test.ts +++ b/src/domain/sourceToBundleComparison.test.ts @@ -6,10 +6,7 @@ import { } from "./javascriptApplicationGraph.js"; import { createHistoricalSourceGraph } from "./referenceSourceGraph.js"; import { compareSourceToBundle } from "./sourceToBundleComparison.js"; -import { - compareSourceToBundleInputSchema, - sourceToBundleComparisonResultSchema, -} from "./sourceToBundleComparisonSchemas.js"; +import { sourceToBundleComparisonResultSchema } from "./sourceToBundleComparisonSchemas.js"; import { artifactEvidence } from "./javascriptApplicationGraph.fixture.js"; const HASH = { @@ -149,10 +146,6 @@ describe("historical source to bundle comparison", () => { candidate_evaluations: 2, }); }); - - it("does not expose caller-selected comparison limits", () => { - expect(compareSourceToBundleInputSchema.shape).not.toHaveProperty("limits"); - }); }); const historicalGraph = ( diff --git a/src/domain/sourceToBundleComparison.ts b/src/domain/sourceToBundleComparison.ts index 63e7a6a9c3..24d43df9a4 100644 --- a/src/domain/sourceToBundleComparison.ts +++ b/src/domain/sourceToBundleComparison.ts @@ -166,7 +166,7 @@ const comparisonCoverage = ( }); const scoringModel = (): SourceToBundleComparisonResult["scoring"] => ({ - algorithm: "rea-source-to-bundle-signals/v1", + algorithm: "rea-source-to-bundle-signals", minimum_candidate_score: 20, weights: SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS.map(([signal, weight]) => ({ signal, diff --git a/src/domain/sourceToBundleComparisonSchemas.ts b/src/domain/sourceToBundleComparisonSchemas.ts index 55d37748c8..2955fe4c82 100644 --- a/src/domain/sourceToBundleComparisonSchemas.ts +++ b/src/domain/sourceToBundleComparisonSchemas.ts @@ -81,7 +81,7 @@ export const sourceToBundleComparisonResultSchema = z.strictObject({ root_artifact_sha256: digestSchema, }), scoring: z.strictObject({ - algorithm: z.literal("rea-source-to-bundle-signals/v1"), + algorithm: z.literal("rea-source-to-bundle-signals"), minimum_candidate_score: z.literal(20), weights: z.array( z.strictObject({ diff --git a/src/domain/staticRuntimeCorrelation.ts b/src/domain/staticRuntimeCorrelation.ts index e0c53d6ca8..1159dceddb 100644 --- a/src/domain/staticRuntimeCorrelation.ts +++ b/src/domain/staticRuntimeCorrelation.ts @@ -145,12 +145,12 @@ type ComparisonIdentity = readonly [string | readonly string[], string, string]; const EXPECTED: Readonly> = { compare_artifacts: [ - "rea.artifact-comparison/v1", + "rea.artifact-comparison", "rea-artifact-comparison", "REA artifact comparison", ], compare_functions: [ - "rea.function-comparison/v1", + "rea.function-comparison", "rea-function-comparison", "REA function comparison", ], diff --git a/src/domain/webBundleAnalysis.ts b/src/domain/webBundleAnalysis.ts index 8c2f32fb84..0076995f57 100644 --- a/src/domain/webBundleAnalysis.ts +++ b/src/domain/webBundleAnalysis.ts @@ -1,38 +1,15 @@ import { z } from "zod"; -import { - DEFAULT_BROWSER_INSPECTION_LIMITS, - inspectWebPageWithSourceInputSchema, -} from "./browserObservation.js"; +import { inspectWebPageWithSourceInputSchema } from "./browserObservation.js"; import { webTextArtifactSchema } from "./webContentArtifact.js"; -/** Provider-owned parser and network safety bounds, never caller settings. */ -export const WEB_BUNDLE_ANALYSIS_LIMITS = { - max_ast_nodes: 250_000, - max_source_maps: 100, - max_source_map_bytes: 4 * 1_024 * 1_024, - max_total_source_map_bytes: 16 * 1_024 * 1_024, - max_source_map_mappings: 10_000, -} as const; - /** Capture-and-analyze input with optional source-map fetching. */ export const analyzeWebBundleToolInputSchema = inspectWebPageWithSourceInputSchema.safeExtend({ fetch_source_maps: z.boolean().default(false), }); -export const analyzeWebBundleInputSchema = - analyzeWebBundleToolInputSchema.transform((input) => ({ - ...input, - limits: DEFAULT_BROWSER_INSPECTION_LIMITS, - analysis_limits: WEB_BUNDLE_ANALYSIS_LIMITS, - })) as z.ZodType; -export type AnalyzeWebBundleInput = z.infer< - typeof inspectWebPageWithSourceInputSchema -> & { - readonly limits: typeof DEFAULT_BROWSER_INSPECTION_LIMITS; - readonly fetch_source_maps: boolean; - readonly analysis_limits: typeof WEB_BUNDLE_ANALYSIS_LIMITS; -}; +export const analyzeWebBundleInputSchema = analyzeWebBundleToolInputSchema; +export type AnalyzeWebBundleInput = z.infer; const sourceLocationSchema = z.object({ script_key: z.string(), @@ -93,12 +70,7 @@ const sourceMapSchema = z.union([ z.object({ ...sourceMapContextShape, ...parsedSourceMapShape, - status: z.literal("truncated"), - limitation: z.string(), - }), - z.object({ - ...sourceMapContextShape, - status: z.enum(["fetch_failed", "invalid", "policy_filtered", "truncated"]), + status: z.enum(["fetch_failed", "invalid", "policy_filtered"]), artifact: z.null(), original_sources: z.tuple([]), original_module_edges: z.tuple([]), @@ -117,24 +89,15 @@ const webTextArtifactSummarySchema = z.object({ export const webSourceMapsSchema = z .object({ - status: z.enum([ - "not_requested", - "included", - "partial", - "unavailable", - "truncated", - ]), + status: z.enum(["not_requested", "included", "partial", "unavailable"]), requested: z.number().int().min(0), processed: z.number().int().min(0), - dropped: z.number().int().min(0), - dropped_script_keys: z.array(z.string()), items: z.array(sourceMapSchema), }) .superRefine((sourceMaps, context) => { if ( - sourceMaps.requested !== sourceMaps.processed + sourceMaps.dropped || - sourceMaps.processed !== sourceMaps.items.length || - sourceMaps.dropped !== sourceMaps.dropped_script_keys.length + sourceMaps.requested !== sourceMaps.processed || + sourceMaps.processed !== sourceMaps.items.length ) context.addIssue({ code: "custom", @@ -142,17 +105,14 @@ export const webSourceMapsSchema = z }); const statuses = sourceMaps.items.map(({ status }) => status); const included = statuses.filter((status) => status === "included").length; - const hasTruncated = statuses.includes("truncated"); const allowedStatuses = - sourceMaps.dropped > 0 || hasTruncated - ? ["truncated"] - : sourceMaps.items.length === 0 - ? ["not_requested", "unavailable"] - : included === sourceMaps.items.length - ? ["included"] - : included > 0 - ? ["partial"] - : ["unavailable"]; + sourceMaps.items.length === 0 + ? ["not_requested", "unavailable"] + : included === sourceMaps.items.length + ? ["included"] + : included > 0 + ? ["partial"] + : ["unavailable"]; if (!allowedStatuses.includes(sourceMaps.status)) context.addIssue({ code: "custom", @@ -164,7 +124,7 @@ export const webSourceMapsSchema = z export type WebSourceMapItem = z.infer; export type WebSourceMaps = z.infer; -/** Provider-neutral result of bounded JavaScript bundle reverse engineering. */ +/** Provider-neutral result of JavaScript bundle reverse engineering. */ export const webBundleAnalysisSchema = z.object({ capture: z.object({ target_url: z.string(), @@ -226,7 +186,7 @@ export const webBundleAnalysisSchema = z.object({ }), ), completeness: z.object({ - status: z.enum(["complete_within_limits", "truncated", "partial"]), + status: z.enum(["complete", "partial"]), parsed_scripts: z.number().int().min(0), parse_failures: z.number().int().min(0), visited_ast_nodes: z.number().int().min(0), diff --git a/src/domain/webBundleAnalyzer.test.ts b/src/domain/webBundleAnalyzer.test.ts index 5113d92b12..46bc125eeb 100644 --- a/src/domain/webBundleAnalyzer.test.ts +++ b/src/domain/webBundleAnalyzer.test.ts @@ -1,20 +1,14 @@ import { describe, expect, it } from "vitest"; -import { - inspectWebPageToolInputSchema, - webPageInspectionSchema, -} from "./browserObservation.js"; +import { webPageInspectionSchema } from "./browserObservation.js"; import { analyzeCapturedWebBundle } from "./webBundleAnalyzer.js"; -import { - analyzeWebBundleInputSchema, - webBundleAnalysisSchema, -} from "./webBundleAnalysis.js"; +import { webBundleAnalysisSchema } from "./webBundleAnalysis.js"; import { createWebTextArtifact } from "./webContentArtifact.js"; const origin = "https://app.example.test"; describe("web bundle analyzer", () => { - it("extracts bounded graph, route, endpoint, vendor, and WebMCP evidence", () => { + it("extracts graph, route, endpoint, vendor, and WebMCP evidence", () => { const source = ` import { createApp } from "./chunk.js?token=secret"; const lazy = import("./lazy.js"); @@ -33,7 +27,7 @@ describe("web bundle analyzer", () => { }); const __webpack_require__ = () => createApp(routes, lazy); `; - const result = analyzeCapturedWebBundle(inspection(source), input()); + const result = analyzeCapturedWebBundle(inspection(source)); expect(result.observations.chunks.edges).toEqual( expect.arrayContaining([ @@ -70,12 +64,12 @@ describe("web bundle analyzer", () => { ]), ); expect(JSON.stringify(result)).not.toContain("authorization=secret"); - expect(result.completeness.status).toBe("complete_within_limits"); + expect(result.completeness.status).toBe("complete"); }); it("reports parser gaps without imposing a finding cap", () => { const malformed = inspection("function broken( {"); - const failed = analyzeCapturedWebBundle(malformed, input()); + const failed = analyzeCapturedWebBundle(malformed); expect(failed.completeness).toMatchObject({ status: "partial", parse_failures: 1, @@ -84,47 +78,52 @@ describe("web bundle analyzer", () => { const analyzed = analyzeCapturedWebBundle( inspection("fetch('/one'); fetch('/two'); fetch('/three');"), - input(), ); expect(analyzed.observations.endpoints).toHaveLength(3); - expect(analyzed.completeness.status).toBe("complete_within_limits"); + expect(analyzed.completeness.status).toBe("complete"); }); - it("propagates source-map truncation into completeness and unknowns", () => { - const analysis = analyzeCapturedWebBundle( - inspection("export {};"), - input(), - { - status: "truncated", - requested: 2, - processed: 1, - dropped: 1, - dropped_script_keys: [`scr_${"2".repeat(64)}`], - items: [ - { - script_key: `scr_${"1".repeat(64)}`, - declared_url: "https://app.example.test/app.js.map", - status: "truncated", - artifact: null, - original_sources: [], - original_module_edges: [], - mappings: [], - limitation: "Source-map response exceeded the byte budget.", - }, - ], - }, - ); + it("analyzes AST nodes after the former fixed node ceiling", () => { + const source = `${";".repeat(250_001)}fetch('/last');`; + const result = analyzeCapturedWebBundle(inspection(source)); - expect(analysis.completeness.status).toBe("truncated"); + expect(result.observations.endpoints).toContainEqual( + expect.objectContaining({ value: "/last" }), + ); + expect(result.completeness.visited_ast_nodes).toBeGreaterThan(250_000); + expect(result.completeness.status).toBe("complete"); + }); + + it("reports unavailable source maps as partial without dropping requested maps", () => { + const analysis = analyzeCapturedWebBundle(inspection("export {};"), { + status: "unavailable", + requested: 1, + processed: 1, + items: [ + { + script_key: `scr_${"1".repeat(64)}`, + declared_url: "https://app.example.test/app.js.map", + status: "fetch_failed", + artifact: null, + original_sources: [], + original_module_edges: [], + mappings: [], + limitation: "Source-map fetch failed.", + }, + ], + }); + + expect(analysis.completeness.status).toBe("partial"); expect(analysis.unknowns).toContainEqual({ dimension: "source_maps", - reason: "Source-map evidence was truncated by provider safety limits", - affected_script_keys: [`scr_${"1".repeat(64)}`, `scr_${"2".repeat(64)}`], + reason: + "One or more requested source maps were unavailable or incomplete", + affected_script_keys: [`scr_${"1".repeat(64)}`], }); }); it("rejects inconsistent source-map coverage counts", () => { - const result = analyzeCapturedWebBundle(inspection("export {};"), input()); + const result = analyzeCapturedWebBundle(inspection("export {};")); expect( webBundleAnalysisSchema.safeParse({ ...result, @@ -140,17 +139,6 @@ describe("web bundle analyzer", () => { }); }); -const input = (overrides: Record = {}) => - analyzeWebBundleInputSchema.parse({ - ...inspectWebPageToolInputSchema.parse({ - cdp_endpoint: "http://127.0.0.1:9222", - allowed_origins: [origin], - target_id: "page-1", - include_script_sources: true, - }), - ...overrides, - }); - const inspection = (source: string) => webPageInspectionSchema.parse({ browser: { @@ -200,7 +188,6 @@ const inspection = (source: string) => status: "not_approved", retained_bytes: 0, excluded_fields: 0, - truncated_fields: 0, }, nodes: [], }, diff --git a/src/domain/webBundleAnalyzer.ts b/src/domain/webBundleAnalyzer.ts index e87aecb74d..14e40ad05e 100644 --- a/src/domain/webBundleAnalyzer.ts +++ b/src/domain/webBundleAnalyzer.ts @@ -5,27 +5,21 @@ import { isIncludedScript, } from "./webBundleAnalyzerInspection.js"; import { buildWebBundleAnalysis } from "./webBundleAnalyzerResult.js"; -import type { - AnalyzeWebBundleInput, - WebBundleAnalysis, -} from "./webBundleAnalysis.js"; +import type { WebBundleAnalysis } from "./webBundleAnalysis.js"; /** Analyze captured, explicitly approved JavaScript source without execution. */ export const analyzeCapturedWebBundle = ( inspection: WebPageInspection, - input: AnalyzeWebBundleInput, sourceMaps: WebBundleAnalysis["observations"]["source_maps"] = { status: "not_requested", requested: 0, processed: 0, - dropped: 0, - dropped_script_keys: [], items: [], }, ): WebBundleAnalysis => { const accumulator = emptyAccumulator(); const sourceScripts = inspection.scripts.items.filter(isIncludedScript); - for (const script of sourceScripts) analyzeScript(script, input, accumulator); + for (const script of sourceScripts) analyzeScript(script, accumulator); return buildWebBundleAnalysis( inspection, sourceScripts, diff --git a/src/domain/webBundleAnalyzerInspection.ts b/src/domain/webBundleAnalyzerInspection.ts index d481721cde..669861b010 100644 --- a/src/domain/webBundleAnalyzerInspection.ts +++ b/src/domain/webBundleAnalyzerInspection.ts @@ -3,10 +3,7 @@ import * as t from "@babel/types"; import { sanitizeEndpointCandidate } from "./browserObservation.js"; import type { WebPageInspection } from "./browserObservation.js"; -import type { - AnalyzeWebBundleInput, - WebBundleAnalysis, -} from "./webBundleAnalysis.js"; +import type { WebBundleAnalysis } from "./webBundleAnalysis.js"; import { calleeName, endpointArgument, @@ -40,12 +37,10 @@ export interface AnalysisAccumulator { visitedNodes: number; parsedScripts: number; parseFailures: number; - astLimitReached: boolean; } export const analyzeScript = ( script: IncludedScript, - input: AnalyzeWebBundleInput, accumulator: AnalysisAccumulator, ): void => { let file: ReturnType; @@ -63,10 +58,6 @@ export const analyzeScript = ( detectVendorFingerprints(script, accumulator); t.traverseFast(file, (node) => { accumulator.visitedNodes += 1; - if (accumulator.visitedNodes > input.analysis_limits.max_ast_nodes) { - accumulator.astLimitReached = true; - return t.traverseFast.stop; - } inspectNode(script, node, accumulator); return undefined; }); @@ -348,7 +339,6 @@ export const emptyAccumulator = (): AnalysisAccumulator => ({ visitedNodes: 0, parsedScripts: 0, parseFailures: 0, - astLimitReached: false, }); export const isIncludedScript = ( diff --git a/src/domain/webBundleAnalyzerResult.ts b/src/domain/webBundleAnalyzerResult.ts index 18fb29c667..f11a3a1b99 100644 --- a/src/domain/webBundleAnalyzerResult.ts +++ b/src/domain/webBundleAnalyzerResult.ts @@ -23,8 +23,6 @@ export const buildWebBundleAnalysis = ( accumulator.parseFailures > 0 || unavailable.length > 0 || sourceMapIncomplete; - const truncated = - accumulator.astLimitReached || sourceMaps.status === "truncated"; return webBundleAnalysisSchema.parse({ capture: buildCaptureObservation(inspection, sourceScripts), observations: { @@ -45,7 +43,6 @@ export const buildWebBundleAnalysis = ( sourceMaps, }), completeness: buildCompleteness({ - truncated, partial, parsedScripts: accumulator.parsedScripts, parseFailures: accumulator.parseFailures, @@ -116,23 +113,16 @@ const buildUnknowns = (input: UnknownsInput): WebBundleAnalysis["unknowns"] => [ { dimension: "source_maps" as const, reason: - input.sourceMaps.status === "truncated" - ? "Source-map evidence was truncated by provider safety limits" - : "One or more requested source maps were unavailable or incomplete", - affected_script_keys: [ - ...new Set([ - ...input.sourceMaps.items - .filter(({ status }) => status !== "included") - .map(({ script_key }) => script_key), - ...input.sourceMaps.dropped_script_keys, - ]), - ].sort(), + "One or more requested source maps were unavailable or incomplete", + affected_script_keys: input.sourceMaps.items + .filter(({ status }) => status !== "included") + .map(({ script_key }) => script_key) + .sort(), }, ]), ]; interface CompletenessInput { - readonly truncated: boolean; readonly partial: boolean; readonly parsedScripts: number; readonly parseFailures: number; @@ -142,11 +132,7 @@ interface CompletenessInput { const buildCompleteness = ( input: CompletenessInput, ): WebBundleAnalysis["completeness"] => ({ - status: input.truncated - ? "truncated" - : input.partial - ? "partial" - : "complete_within_limits", + status: input.partial ? "partial" : "complete", parsed_scripts: input.parsedScripts, parse_failures: input.parseFailures, visited_ast_nodes: input.visitedNodes, diff --git a/src/server/registerArtifactComparisonTool.ts b/src/server/registerArtifactComparisonTool.ts index b1ae6451d8..2dc5970e65 100644 --- a/src/server/registerArtifactComparisonTool.ts +++ b/src/server/registerArtifactComparisonTool.ts @@ -25,7 +25,7 @@ export const registerArtifactComparisonTool = ( async (input, context) => { const expected = { operation: ["inspect_artifact", "inventory_artifact"], - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }; const left = resolveSessionEvidenceIds( session, @@ -47,7 +47,7 @@ export const registerArtifactComparisonTool = ( const leftEvidenceIds = [input.left_evidence_id]; const rightEvidenceIds = [input.right_evidence_id]; const evidence = createEvidence(undefined, ARTIFACT_COMPARISON_PROVIDER, { - predicateType: "rea.artifact-comparison/v1", + predicateType: "rea.artifact-comparison", operation: contract.name, parameters: { left_evidence_ids: leftEvidenceIds, diff --git a/src/server/registerBundleComparisonTool.ts b/src/server/registerBundleComparisonTool.ts index fd9ae02ba4..a035f1eb89 100644 --- a/src/server/registerBundleComparisonTool.ts +++ b/src/server/registerBundleComparisonTool.ts @@ -36,7 +36,7 @@ export const registerBundleComparisonTool = ( if (!computed.ok) return toCallToolResult(computed, contract); const comparison = computed.value; const evidence = createEvidence(undefined, BUNDLE_COMPARISON_PROVIDER, { - predicateType: "rea.bundle-comparison/v1", + predicateType: "rea.bundle-comparison", operation: contract.name, parameters: { left_bundle_sha256: comparison.left_bundle_sha256, diff --git a/src/server/registerFunctionComparisonTool.ts b/src/server/registerFunctionComparisonTool.ts index 9bc54ba252..cc661175ce 100644 --- a/src/server/registerFunctionComparisonTool.ts +++ b/src/server/registerFunctionComparisonTool.ts @@ -25,7 +25,7 @@ export const registerFunctionComparisonTool = ( async (input, context) => { const expected = { operation: "analyze_function", - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }; const left = resolveSessionEvidenceIds( session, @@ -51,7 +51,7 @@ export const registerFunctionComparisonTool = ( if (!computed.ok) return toCallToolResult(computed, contract); const comparison = computed.value; const evidence = createEvidence(undefined, FUNCTION_COMPARISON_PROVIDER, { - predicateType: "rea.function-comparison/v1", + predicateType: "rea.function-comparison", operation: contract.name, parameters: { left_evidence_id: leftEvidence.evidence_id, diff --git a/src/server/registerJavaScriptRuntimeObservationTools.ts b/src/server/registerJavaScriptRuntimeObservationTools.ts index 7b036552ce..1712a97135 100644 --- a/src/server/registerJavaScriptRuntimeObservationTools.ts +++ b/src/server/registerJavaScriptRuntimeObservationTools.ts @@ -75,7 +75,7 @@ export const registerJavaScriptRuntimeObservationTools = ( [ { operation: "list_javascript_runtime_targets", - predicate: "rea.javascript-runtime-target-list/v1", + predicate: "rea.javascript-runtime-target-list", }, ], ); diff --git a/src/server/registerManagedWorkflowTools/evidence.ts b/src/server/registerManagedWorkflowTools/evidence.ts index aaf2a8ffd2..b3f8625367 100644 --- a/src/server/registerManagedWorkflowTools/evidence.ts +++ b/src/server/registerManagedWorkflowTools/evidence.ts @@ -10,7 +10,7 @@ export const resolveManagedEvidence = ( ): Result => resolveSessionEvidenceIds(session, evidenceIds, { operation: "inspect_managed_members", - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }); export const resolveManagedArtifactEvidence = ( @@ -19,7 +19,7 @@ export const resolveManagedArtifactEvidence = ( ): Result => resolveSessionEvidenceIds(session, [evidenceId], { operation: "inspect_managed_artifact", - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }); export const resolveManagedBoundaryEvidence = ( @@ -28,7 +28,7 @@ export const resolveManagedBoundaryEvidence = ( ): Result => resolveSessionEvidenceIds(session, [evidenceId], { operation: "inspect_managed_native_boundaries", - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }); export const resolveNativeEvidence = ( @@ -44,7 +44,7 @@ export const resolveNativeEvidence = ( : "inspect_macho or analyze_function"; const resolved = resolveSessionEvidenceIds(session, [evidenceId], { operation: expectedOperation, - predicate: "rea.analysis/v2", + predicate: "rea.analysis", }); if (!resolved.ok) return resolved; records.push(...resolved.value); diff --git a/tests/acceptance/applications/applicationExportShapeMcp.test.ts b/tests/acceptance/applications/applicationExportShapeMcp.test.ts index 0d871bad51..8e5a06a6d3 100644 --- a/tests/acceptance/applications/applicationExportShapeMcp.test.ts +++ b/tests/acceptance/applications/applicationExportShapeMcp.test.ts @@ -75,7 +75,7 @@ describe("application workflow MCP parity", () => { expect.arrayContaining([ expect.objectContaining({ operation: "compare_javascript_export_shapes", - predicate_type: "rea.javascript-export-shape-comparison/v1", + predicate_type: "rea.javascript-export-shape-comparison", }), ]), ); diff --git a/tests/acceptance/applications/applicationReplayMcp.test.ts b/tests/acceptance/applications/applicationReplayMcp.test.ts index 2007bfd1d7..fe47b55855 100644 --- a/tests/acceptance/applications/applicationReplayMcp.test.ts +++ b/tests/acceptance/applications/applicationReplayMcp.test.ts @@ -243,10 +243,10 @@ async function executeCharacterizationScenario( expect(session.exportEvidenceBundle().records).toEqual( expect.arrayContaining([ expect.objectContaining({ - predicate_type: "rea.javascript-export-transformation/v1", + predicate_type: "rea.javascript-export-transformation", }), expect.objectContaining({ - predicate_type: "rea.runtime-characterization/v1", + predicate_type: "rea.runtime-characterization", }), ]), ); diff --git a/tests/acceptance/applications/applicationWorkflowCli.test.ts b/tests/acceptance/applications/applicationWorkflowCli.test.ts index bd098ac25d..b9571dad2d 100644 --- a/tests/acceptance/applications/applicationWorkflowCli.test.ts +++ b/tests/acceptance/applications/applicationWorkflowCli.test.ts @@ -108,7 +108,7 @@ describe("application workflow CLI parity", () => { ]); expect(traced).toMatchObject({ operation: "trace_javascript_semantics", - predicate_type: "rea.javascript-semantic-trace/v1", + predicate_type: "rea.javascript-semantic-trace", normalized_result: { source_evidence_id: analyzed.value.evidence_id, source_graph_id: result.semantic_graph.graph_id, @@ -265,7 +265,7 @@ describe("application workflow CLI export Evidence", () => { ); expect(compared).toMatchObject({ operation: "compare_javascript_export_shapes", - predicate_type: "rea.javascript-export-shape-comparison/v1", + predicate_type: "rea.javascript-export-shape-comparison", normalized_result: { summary: { added: 1, removed: 0, changed: 0, unknown: 0 }, changes: [ diff --git a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts index e86b865c42..03befa093f 100644 --- a/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts +++ b/tests/boundary/filesystem/javascriptExportShapeComparison.test.ts @@ -240,7 +240,7 @@ describe("JavaScript export return-shape projection", () => { }); }); - it("keeps incomplete source property coverage partial", async () => { + it("returns every source property within the supplied inputs", async () => { const properties = [ 'a_type: "item"', ...Array.from( @@ -254,7 +254,7 @@ describe("JavaScript export return-shape projection", () => { }); const result = compare(left, right); - expect(result.coverage.status).toBe("partial"); + expect(result.coverage.status).toBe("complete-within-inputs"); expect(result.coverage.left_omitted_fields).toBe(0); expect(result.coverage.right_omitted_fields).toBe(0); }); diff --git a/tests/composition/analysis-sessions/artifactComparison.test.ts b/tests/composition/analysis-sessions/artifactComparison.test.ts index 750ded2471..68b09c40d8 100644 --- a/tests/composition/analysis-sessions/artifactComparison.test.ts +++ b/tests/composition/analysis-sessions/artifactComparison.test.ts @@ -39,6 +39,26 @@ const observe = async (path: string) => { ); }; +const evidencePages = ( + inventory: Awaited>, + side: string, +) => + Array.from({ length: 101 }, (_, index) => + createEvidence( + { + path: `${side}-${String(index)}`, + sha256: inventory.subject?.digest.sha256 ?? "0".repeat(64), + format: inventory.subject?.format ?? "directory", + }, + PROVIDER, + { + operation: "inventory_artifact", + parameters: { page: index, side }, + result: jsonValueSchema.parse(inventory.normalized_result), + }, + ), + ); + describe("artifact comparison", () => { it("classifies deterministic path changes and cites both inventories", async () => { const parent = await createTestTempDirectory("rea-artifact-compare-"); @@ -144,6 +164,28 @@ describe("artifact comparison", () => { }); }, 15_000); + it("retains every inventory citation when comparing many evidence pages", async () => { + const parent = await createTestTempDirectory( + "rea-artifact-pages-evidence-", + ); + const leftPath = join(parent, "left.app"); + const rightPath = join(parent, "right.app"); + await Promise.all([mkdir(leftPath), mkdir(rightPath)]); + await Promise.all([ + writeFile(join(leftPath, "main.js"), "left();"), + writeFile(join(rightPath, "main.js"), "right();"), + ]); + + const leftInventory = await observe(leftPath); + const rightInventory = await observe(rightPath); + const comparison = compareArtifacts( + evidencePages(leftInventory, "left"), + evidencePages(rightInventory, "right"), + ); + + expect(comparison.changes[0]?.evidence_links).toHaveLength(202); + }); + it("rejects non-inventory and tampered Evidence", async () => { const root = await createTestTempDirectory("rea-artifact-invalid-"); const evidence = await observe(root); diff --git a/tests/composition/analysis-sessions/managedApplicationGraph.test.ts b/tests/composition/analysis-sessions/managedApplicationGraph.test.ts index 6b1bfe7f76..159d3fb808 100644 --- a/tests/composition/analysis-sessions/managedApplicationGraph.test.ts +++ b/tests/composition/analysis-sessions/managedApplicationGraph.test.ts @@ -84,7 +84,7 @@ describe("managed application graph projection", () => { const parsed = parseEvidence(evidence.value); expect(parsed).toMatchObject({ operation: "project_managed_application_graph", - predicate_type: "rea.managed-application-graph/v1", + predicate_type: "rea.managed-application-graph", provider: { id: "rea-dotnet-workflows" }, confidence: "inferred", authority: "analyst-inference",