Summary
The packaged-import contract added through #15714 / PR #15718 has two coupled gaps:
- a literal dynamic parent-root import such as
import('../ai/...') or import('../src/...') escapes the current static-only guard; and
- the shared
extractLiteralImportSpecifiers() helper is not yet syntax-accurate. Its comment-stripped regex also recognizes import-shaped text inside ordinary string literals.
The second fact falsifies the original one-line repair. Projecting the current helper directly into the parent-root assertion would catch the deployment dead end while creating false-positive build failures.
Why this matters
A forbidden parent-root dependency can pass the packaging test yet remain absent from the packaged local closure. The installed application then fails only when that dynamic import executes from app.asar.
Conversely, an ordinary string containing import-shaped prose can currently be misclassified as a dependency. A guard built directly on that output can reject valid source. Both are packaging-boundary correctness failures; neither should be deferred to an installed runtime.
Verified current reality
At merged dev@86fc94cd87b569f8359bae75ed2d52e4974936c3:
extractLiteralImportSpecifiers() removes comments and applies IMPORT_SPECIFIER_RE; it is a bounded regex scanner, not a JavaScript syntax parser.
import('../ai/example.mjs') yields ['../ai/example.mjs'].
- an ordinary string containing
import('../src/string.mjs') also yields ['../src/string.mjs'].
- non-literal
import(runtimeSpecifier) remains outside the declared contract.
- the parent-root assertion still matches only static
from '../ai|src/...' syntax.
acorn is already a direct repository dependency. An executable parse probe distinguishes the literal dynamic import as an ImportExpression while the lookalike remains an ordinary VariableDeclaration.
ADR-0034 §§2.5–2.6 require an explicit packaged source graph and dev/prod parity. They do not justify either a false-negative dependency guard or a false-positive regex approximation.
Intended solution
Harden the shared literal-import extractor so it derives specifiers from JavaScript syntax rather than import-shaped text. Use the repository's existing parser substrate (currently acorn) or an equivalently precise already-owned lexer; do not add a second regex scanner.
The shared extractor must cover:
- static import declarations;
- side-effect imports;
- re-export sources;
- string-literal dynamic
import() expressions; and
- the staged
.mjs, .cjs, and .js source forms consumed by packaging.
It must ignore comments, ordinary strings/template text, and non-literal dynamic specifiers. Once that authority is syntax-accurate, project its output into the existing packaging-test seam and explicitly reject normalized ../ai/ and ../src/ specifiers. Keep local ./*.mjs closure behavior unchanged.
Acceptance criteria
Out of scope
- Bundling all of
ai/ or src/.
- Resolving computed or otherwise non-literal dynamic specifiers.
- Reworking the packaged local dependency model established by #15714.
- Changes to runtime import semantics outside the packaging guard.
- Adding a new parser dependency when an owned parser can satisfy the contract.
Negative-ROI check
Positive ROI after narrowing. The scanner is already shared by local-closure and bare-package discovery, so repairing syntax accuracy at that authority avoids three divergent guards. A full packer redesign or a second parser would be negative ROI.
Related substrate
Duplicate and content sweep
Live sweep completed at 2026-07-22T14:37:35Z across the latest open issue queue plus targeted issue/PR searches for dynamic parent-root imports and packaged app.asar guards. No duplicate ticket or implementation was found. Revalidated after PR #15718 merged.
Decision record
No new architecture decision. This ticket repairs the syntax authority used to enforce the packaged source boundary already established by #15714 and governed by ADR-0034.
Summary
The packaged-import contract added through #15714 / PR #15718 has two coupled gaps:
import('../ai/...')orimport('../src/...')escapes the current static-only guard; andextractLiteralImportSpecifiers()helper is not yet syntax-accurate. Its comment-stripped regex also recognizes import-shaped text inside ordinary string literals.The second fact falsifies the original one-line repair. Projecting the current helper directly into the parent-root assertion would catch the deployment dead end while creating false-positive build failures.
Why this matters
A forbidden parent-root dependency can pass the packaging test yet remain absent from the packaged local closure. The installed application then fails only when that dynamic import executes from
app.asar.Conversely, an ordinary string containing import-shaped prose can currently be misclassified as a dependency. A guard built directly on that output can reject valid source. Both are packaging-boundary correctness failures; neither should be deferred to an installed runtime.
Verified current reality
At merged
dev@86fc94cd87b569f8359bae75ed2d52e4974936c3:extractLiteralImportSpecifiers()removes comments and appliesIMPORT_SPECIFIER_RE; it is a bounded regex scanner, not a JavaScript syntax parser.import('../ai/example.mjs')yields['../ai/example.mjs'].import('../src/string.mjs')also yields['../src/string.mjs'].import(runtimeSpecifier)remains outside the declared contract.from '../ai|src/...'syntax.acornis already a direct repository dependency. An executable parse probe distinguishes the literal dynamic import as anImportExpressionwhile the lookalike remains an ordinaryVariableDeclaration.ADR-0034 §§2.5–2.6 require an explicit packaged source graph and dev/prod parity. They do not justify either a false-negative dependency guard or a false-positive regex approximation.
Intended solution
Harden the shared literal-import extractor so it derives specifiers from JavaScript syntax rather than import-shaped text. Use the repository's existing parser substrate (currently
acorn) or an equivalently precise already-owned lexer; do not add a second regex scanner.The shared extractor must cover:
import()expressions; and.mjs,.cjs, and.jssource forms consumed by packaging.It must ignore comments, ordinary strings/template text, and non-literal dynamic specifiers. Once that authority is syntax-accurate, project its output into the existing packaging-test seam and explicitly reject normalized
../ai/and../src/specifiers. Keep local./*.mjsclosure behavior unchanged.Acceptance criteria
import('../ai/example.mjs')fails the parent-root guard.import('../src/example.mjs')fails the parent-root guard.extractLiteralImportSpecifiers()derives imports from syntax, not import-lookalike text.import(...),from '...', orexport ... from '...'do not create specifiers..mjs,.cjs, and.jssources../*.mjsclosure discovery and the installedapp.asarimport witness remain green.Out of scope
ai/orsrc/.Negative-ROI check
Positive ROI after narrowing. The scanner is already shared by local-closure and bare-package discovery, so repairing syntax accuracy at that authority avoids three divergent guards. A full packer redesign or a second parser would be negative ROI.
Related substrate
Duplicate and content sweep
Live sweep completed at 2026-07-22T14:37:35Z across the latest open issue queue plus targeted issue/PR searches for dynamic parent-root imports and packaged
app.asarguards. No duplicate ticket or implementation was found. Revalidated after PR #15718 merged.Decision record
No new architecture decision. This ticket repairs the syntax authority used to enforce the packaged source boundary already established by #15714 and governed by ADR-0034.