Skip to main content
← Back to list
01Issue
BugIn ProgressSwamp CLI
Assigneesstack72

DatastoreProvider.resolveCachePath declared optional but silently required at runtime

Opened by stack72 · 4/22/2026

Summary

DatastoreProvider.resolveCachePath is declared as optional in the TypeScript interface (resolveCachePath?(repoDir: string): string | undefined), but swamp core's runtime consumer treats "method absent" and "method returning undefined" as different code paths. Omitting the method entirely — which the type system permits — silently breaks datastore sync --pull: the command exits 0 but the reader's catalog is empty.

Impact

Regression shipped in @swamp/gcs-datastore 2026.04.21.1 (commit a30741ea) where resolveCachePath was removed because the author believed returning undefined and omitting the method were equivalent. It compiled clean against the optional interface, passed unit tests, and produced 0-result pulls in UAT:

The sibling @swamp/s3-datastore defines the method and returns undefined, so its pull works. Isolates the defect to a TS-interface/runtime-contract mismatch, not to either provider's logic.

Reproduction

Two fresh swamp repos against the same GCS bucket + prefix:

# Writer
swamp init
swamp datastore setup extension @swamp/gcs-datastore \
  --config '{"bucket":"<bucket>","prefix":"<prefix>"}'
# install shell-echo model + single-step workflow
swamp workflow run single-step
swamp data list shell-echo --json     # total: 4

# Reader (separate temp dir, same prefix)
swamp init
swamp datastore setup extension @swamp/gcs-datastore \
  --config '{"bucket":"<bucket>","prefix":"<prefix>"}'
swamp datastore sync --pull            # exits 0
swamp data list shell-echo --json      # total: 0   <-- should be 4

With the extension built from a provider that omits resolveCachePath, the reader's cache lands somewhere swamp data list does not scan. Restoring the method so it returns undefined makes core's repoId-keyed fallback fire and the reader sees all 4 rows.

Root cause

The interface at DatastoreProvider.resolveCachePath? says this is optional. The consumer in swamp core appears to do something like:

if (typeof provider.resolveCachePath === 'function') {
  const override = provider.resolveCachePath(repoDir);
  // ...branch that handles `override === undefined` by using the repoId fallback
} else {
  // different branch — whatever this is, it does NOT produce the same
  // cache path the reader's data.list scans
}

Those two branches must be equivalent for the interface-level "optional" claim to be true. Right now they are not.

Requested fix (pick one)

  1. Make the method strictly required in the interface — drop the ? from DatastoreProvider.resolveCachePath. Forces every provider to be explicit: "I return undefined to get the default" vs. a hard-to-spot omission. This is the cheapest fix and most honest about current runtime semantics.

  2. Unify the two branches in the consumer — treat "method missing" as equivalent to "method returned undefined" and run the same fallback path. Keeps the interface truly optional.

Option 2 is nicer for extension authors; option 1 is safer short-term because it rules out the silent-failure mode by construction.

Workaround already applied downstream

systeminit/swamp-extensions has restored resolveCachePath on the GCS provider (returning undefined, matching S3) and added a contract-level unit test that asserts both typeof provider.resolveCachePath === "function" and the undefined return value, so the regression cannot recur in that repo.

Environment

  • swamp CLI: 20260422.161846.0 (sha 88acb3cc)
  • Affected extensions: @swamp/gcs-datastore (confirmed), any provider that omits the method (latent)
  • Interface file in extensions mirror: datastore/gcs/extensions/datastores/_lib/interfaces.ts:106
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 8 MOREREVIEW+ 2 MOREPR_LINKED

In Progress

4/23/2026, 12:52:11 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack724/23/2026, 12:41:24 AM

Sign in to post a ripple.