Skip to main content
← Back to list
01Issue
FeatureShippedSwamp CLIPublic
AssigneesNone

Relationships

↔ sibling #2856

#2862 Tests: property test for buildMarkDirtyHook path mapping

Opened by stack72 · 9/30/2026· Shipped 10/1/2026

Background (read this first)

swamp is about to rework its datastore layer. The design proposal is design/enablers/datastore-commit-log.md on the datastore-rework branch. It is not merged; §2 of it is a write-path inventory.

How the datastore works today.

  • Repositories write files into the repo's .swamp/ directory, or into the datastore cache dir for a custom datastore. They then call a mark hook, markDirty(relPath?), which reaches DatastoreSyncService (src/domain/datastore/datastore_sync_service.ts, interface at :168, the eight-rule markDirty contract at :199-257).
  • A caller then flushes, through one of four paths:
    • acquireModelLocks().flush (src/cli/repo_context.ts:1723);
    • the global coordinator flushDatastoreSync (src/cli/mod.ts:2251);
    • pushManagedConfigChanges (src/cli/managed_config_sync.ts:45/153);
    • serve's pushChangedToRemote (src/serve/handlers/shared.ts:83-97) under the sync gate.
  • The S3 and GCS sync implementations live in swamp-extensions, not here. They own the dirty set, the .datastore-sync-state.json sidecar, _index shards and _meta.json.
  • A filesystem datastore has no sync service at all (repo_context.ts:1170-1178).
  • Each repo has a SQLite catalog, .swamp/data/_catalog.db, that DataQueryService reads.
  • Serve runs pollers that pull peers' changes: Config, AccessData, RuntimeData.

What changes. The refactor lands on main in phases:

  • Phase 1: a UnitOfWork port with a legacy adapter, so repositories stage writes instead of calling the hook directly.
  • Phase 2: libswamp use cases own the unit of work, and CLI commands and serve stop calling markDirty/pushChanged themselves.
  • Later phases: a new commit-log engine behind an opt-in format.

Phases 1 and 2 must not change any behaviour.

Why this issue exists. Before any of that lands, this repo's own tests have to pin today's behaviour, so a dropped mark, a lost delete or a missed catalog refresh fails a test instead of reaching users. An audit in September 2026 found:

  • no in-memory remote to test sync against;
  • no test that every file a repository changes gets marked, and several real unmarked paths;
  • no two-repo propagation test;
  • no ratchet on the write seams Phase 1 will move;
  • the sync-service conformance suite only checks method shapes.

This issue is one of a set. The tracking issue is listed under Related. End-to-end CLI tests for the same risks are in swamp-club/swamp-uat #481-#485, #487, #490 and #492.

Rules (from AGENTS.md)

  • Unit tests are in-process only: no subprocesses, no process-global mutation; use withMockedEnv.
  • Integration tests in integration/ wire real components on a temp filesystem and must not spawn the CLI.
  • Registry-registered test types use a per-run name built with crypto.randomUUID(), or invalidateType in a finally.
  • No fixed sleeps (use waitFor from @swamp-club/swamp-testing), no wall-clock assertions, and Deno.utime for mtimes.
  • Use @std/path for paths and assertPathEquals for path comparisons, since tests run on Windows too.
  • New files need the AGPLv3 header (deno run license-headers).
  • Pin today's behaviour, including known gaps. When today's behaviour is a bug, assert it as it is, with a comment naming the bug. Prefer an explicit pinned list checked with assertPinnedSet, so a later fix forces the test to be updated on purpose. Do not fix production code in these issues unless the issue says so.

Goal

Add a property test for buildMarkDirtyHook's path mapping. Every mark crosses this function, and the refactor keeps it in the legacy adapter.

Current state

  • buildMarkDirtyHook (src/cli/repo_context.ts:233-252) turns an absolute path into a path relative to the datastore cache, falling back to <repo>/.swamp.
  • A path outside both sends nothing; undefined becomes a bare mark.
  • It is wired only in requireInitializedRepo (:1074) and requireInitializedRepoUnlocked (:1240), for custom datastores with a cachePath.
  • The only tests are examples at src/cli/repo_context_test.ts:3424-3470.
  • There are other property tests, such as paths_property_test.ts and composite_name_property_test.ts, but none for this.

Test

src/cli/repo_context_property_test.ts, using fast-check as the existing *_property_test.ts files do. Properties, over random path segments (including dots, unicode and spaces) under the cache root and under <repo>/.swamp:

  1. The mark is relative to the cache or .swamp, uses forward slashes, never starts with .., is never absolute, and contains no backslash, including when the input uses the platform separator.
  2. Mapping is stable: the same input always gives the same mark.
  3. Any path outside both roots sends no mark, including sibling directories whose names start with the root name (/cache-other versus /cache).
  4. undefined gives exactly one bare mark.
  5. For a path under the cache, joining the mark back onto the cache root gives the original absolute path (a round-trip, compared with assertPathEquals).

Done when

The property test passes on Linux, macOS and Windows path semantics (use @std/path).

Dependencies

Blocked by: nothing, so this can start now
Blocks: nothing
Full dependency graph and waves: swamp-club#2865.

02Bog Flow
✓OPEN✓TRIAGED✓IN PROGRESS✓SHIPPEDLINKED+ 4 MOREPR_MERGED+ 1 MORESESSION_SUMMARIZED

Shipped

10/1/2026, 4:38:33 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 linked sibling of #285610/1/2026, 2:52:10 PM

Sign in to post a ripple.