diff --git a/docs/javascript-artifact-reconstruction.md b/docs/javascript-artifact-reconstruction.md index 539c330ad..ccca4d12f 100644 --- a/docs/javascript-artifact-reconstruction.md +++ b/docs/javascript-artifact-reconstruction.md @@ -221,3 +221,8 @@ paired, ambiguous, and unpaired IPC, validation candidates, utility processes, and native binding requests. These fixtures establish parser and artifact-reader claims; they do not replace the later operator-supplied real-application benchmark. + +URI schemes are classified independently of local file names: a reference such +as `web3:app.js` remains external even when an artifact has that literal name. +Scheme characters may include digits after the initial letter; a relative path +such as `./web3:app.js` still names a local artifact. diff --git a/src/application/javascript/JavaScriptArtifactPathResolution.test.ts b/src/application/javascript/JavaScriptArtifactPathResolution.test.ts index 5223926e8..0653ac8f7 100644 --- a/src/application/javascript/JavaScriptArtifactPathResolution.test.ts +++ b/src/application/javascript/JavaScriptArtifactPathResolution.test.ts @@ -9,6 +9,42 @@ import { artifactLocalIdentity } from "./JavaScriptArtifactGraphContext.js"; import { applicationNodeIdentitySchema } from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js"; import { analyzeJavaScriptStaticSource } from "../../domain/javascript/javascriptStaticAnalysis.js"; +describe("artifact URI scheme classification", () => { + it.each(["html-reference", "module-specifier", "url-reference"] as const)( + "keeps digit-bearing schemes external in %s even when a local file matches", + (context) => { + const files = fileMap([ + file("index.html", "root"), + file("web3:app.js", "root"), + ]); + expect( + resolve({ + declaredPath: "web3:app.js", + sourcePath: "index.html", + context, + files, + }), + ).toMatchObject({ resolution_status: "external", resolved_path: null }); + }, + ); + + it("keeps references under a digit-bearing external base href external", () => { + const files = fileMap([ + file("index.html", "root"), + file("web3:assets/app.js", "root"), + ]); + expect( + resolve({ + declaredPath: "app.js", + sourcePath: "index.html", + context: "html-reference", + htmlBaseHref: "web3:assets/", + files, + }), + ).toMatchObject({ resolution_status: "external", resolved_path: null }); + }); +}); + describe("artifact-local graph identity", () => { it("preserves complete long namespace and key values", () => { const digest = "a".repeat(64); diff --git a/src/domain/artifactPathSyntax.test.ts b/src/domain/artifactPathSyntax.test.ts index 7a31567ae..50eb86c4d 100644 --- a/src/domain/artifactPathSyntax.test.ts +++ b/src/domain/artifactPathSyntax.test.ts @@ -12,6 +12,11 @@ describe("artifact path syntax", () => { expect(hasScheme("https://example.test/app.js")).toBe(true); expect(hasScheme("file:///app.js")).toBe(true); expect(hasScheme("node:fs")).toBe(true); + expect(hasScheme("web3:app.js")).toBe(true); + expect(hasScheme("h323:user@example.test")).toBe(true); + expect(hasScheme("x1+custom.v2-test:asset")).toBe(true); + expect(hasScheme("3web:app.js")).toBe(false); + expect(hasScheme("./web3:app.js")).toBe(false); expect(hasScheme("./relative.js")).toBe(false); expect(hasScheme("/absolute.js")).toBe(false); expect(hasScheme("app.js")).toBe(false); diff --git a/src/domain/artifactPathSyntax.ts b/src/domain/artifactPathSyntax.ts index 46666dcfb..382856a4c 100644 --- a/src/domain/artifactPathSyntax.ts +++ b/src/domain/artifactPathSyntax.ts @@ -17,7 +17,7 @@ /** Whether a reference carries a URI scheme (`https:`, `file:`, …). */ export const hasScheme = (value: string): boolean => - /^[A-Za-z][A-Za-z+.-]*:/u.test(value); + /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value); /** Whether a reference is scheme-qualified or protocol-relative. */ export const looksExternal = (value: string): boolean =>