Skip to main content
← Back to list
01Issue
BugClosedSwamp CLI
AssigneesNone

#214 Port importBundleByPath ENOENT fallback to datastore/driver/vault/report loaders

Opened by keeb · 5/2/2026

Summary

Follow-up to swamp-club#212 / PR systeminit/swamp#1288. The fix in #1288 added two layers of defense against missing cached bundle files:

  1. A bundle-existence gate in findStaleFiles (domain-shared — all six loaders inherit it via bundle_freshness.ts).
  2. An ENOENT fallback in UserModelLoader.importBundleByPath that rebundles from source on demand.

Layer 1 covers all six loaders. Layer 2 (the import-site fallback) was only added to UserModelLoader. The four sibling loaders — UserDatastoreLoader, UserDriverLoader, UserVaultLoader, UserReportLoader — each have their own importBundle / loadSingleType paths that read entry.bundle_path directly with no ENOENT recovery. They benefit from layer 1 transparently, but they still lack the import-site defense for two cases:

  • Concurrent-rm races: a swamp data gc or external rm between findStaleFiles and the loader's import call can ENOENT.
  • Hot-load attach paths analogous to UserModelLoader.attachPendingExtensionsForType that may bypass freshness in some flows.

Reproducer

Same pattern as swamp-club#212, but targeting a sibling kind:

  1. Define a custom datastore (or driver/vault/report) under the appropriate extensions/ path.
  2. Run any command that loads it so the catalog and bundle exist.
  3. While that command is still resolving the type, rm the bundle file out from under it (or rely on swamp data gc to misfire).
  4. The catch path will throw NotFound from Deno.readTextFile(entry.bundle_path) instead of recovering.

The buildIndex path is fully covered by the layer-1 gate, so this is the residual surface only — narrow but a real hole for racy operations.

Suggested fix

Apply the same surgical pattern to each sibling loader's importBundle-equivalent:

  • Catch Deno.errors.NotFound from Deno.readTextFile(entry.bundle_path).
  • Rebundle from source via the loader's existing bundleExtension call (each loader already has one for first-load and stale-rebuild paths).
  • Write the rebundled JS back to entry.bundle_path and continue with the import.
  • Document the single-import-per-process invariant (per #1288's comment).

Each sibling loader has its own recoverMissingBundle shape candidate. Could also consider hoisting the helper into a shared utility — but the loaders' bundle paths and helpers diverge enough that copy-paste is probably cleaner than premature abstraction.

Environment

  • swamp main as of v20260502.153639.0-sha.907c6883 (the merge that landed #1288)
  • Affects all four sibling loaders symmetrically — Linux, macOS, Windows
02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

5/6/2026, 8:19:28 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

stack72 commented 5/4/2026, 4:48:37 PM

Strategic fix lands in W4 of the extension catalog rearchitecture (see #211 for sequence context). This issue is the same "we forgot to do this in four places" pattern as #128 and #209 — duplication across the five user-loader files. W4 collapses the five loaders into a single `KindAdapter`-parameterized path, at which point `importBundleByPath`'s ENOENT fallback (added for models in #212 / PR #1288) becomes one code path covering all five kinds, closing this issue.

Tactical port to the four siblings is not currently planned — porting the fix four times now means deleting four code paths when W4 lands. W4 is several weeks out (third dependent workstream after W1a/W1b and W2/W3), not months.

If you're actively hitting this on a non-model extension (vault, driver, datastore, or report), please comment with the extension and the failure shape. Active user impact is the bar for expediting a tactical port. Absent that signal, this issue stays open as a tracking record and the ENOENT fallback is incorporated into W4's design as a baseline requirement.

Sign in to post a ripple.