Relationships
#555 feat: giga-swamp comprehensive UAT coverage
Opened by stack72 · 6/4/2026
Problem
Giga-swamp is feature-complete (all 7 phases + extensions shipped) but has zero UAT coverage. The existing datastore tests (sync_test.ts, lock_test.ts, setup_test.ts, status_test.ts, compact_test.ts) all operate in solo mode. Namespace commands, namespace-scoped sync, cross-namespace queries, and data migration are untested in the UAT suite.
Previous scoped sync PRs (#1386, #134) passed all unit tests but failed in production. The UAT suite is the only reliable validation for cross-repo datastore scenarios. Without namespace UAT coverage, regressions in giga-swamp will be caught by users, not CI.
Test Infrastructure
All cloud-dependent tests use local containers — no real AWS/GCS credentials needed for development:
- S3 tests use a ministack container (ministack.org) for S3-compatible local testing
- GCS tests use fake-gcs-server for GCS-compatible local testing
- The existing getTestBackends() pattern runs tests against both backends automatically
- Tests skip gracefully when a backend is unavailable
Each phase should be its own commit so git bisect can isolate regressions.
Phase A: Infrastructure + Helpers (commit 1)
Extend the helper infrastructure before writing any tests. Zero risk — purely additive.
New file: src/cli/helpers/datastore_namespace_helpers.ts
Helper functions:
- namespaceSet(runner, cwd, slug) — runs swamp datastore namespace set
- namespaceUnset(runner, cwd, opts?) — runs swamp datastore namespace unset, with optional --migrate --confirm flags
- namespaceMigrate(runner, cwd, opts?) — runs swamp datastore namespace migrate, with optional --confirm and --reverse flags
- namespaceList(runner, cwd) — runs swamp datastore namespaces --json
- catalogPull(runner, cwd, namespaces) — runs swamp datastore catalog pull --namespaces
Extend DatastoreBackend interface:
- setupDatastoreWithNamespace(runner, cwd, prefix, namespace) — same as setupDatastore but adds namespace to the config JSON
Add JSON schemas to schemas.ts:
- NamespaceListSchema, NamespaceSetSchema, NamespaceMigrateSchema
Export everything from mod.ts.
Phase B: Namespace Commands — filesystem only (commit 2)
File: tests/cli/datastore/namespace/commands_test.ts
No cloud credentials needed. Tests against filesystem datastores.
- namespace set happy path — fresh repo + filesystem datastore, verify .swamp.yaml updated, manifest exists
- namespace set validation — invalid slugs (uppercase, special chars, 65 chars, leading hyphen) rejected
- namespace set already set — same value errors
- namespace set conflict — two repos same datastore, second claims same slug, conflict error
- namespace set different slugs — two repos, infra and security, both succeed
- namespaces list empty — no namespaces, empty output
- namespaces list with entries — two namespaces, both listed, current marked
- namespaces JSON mode — validates against NamespaceListSchema
- namespace unset happy path — single namespace, succeeds
- namespace unset with multiple — two namespaces exist, error
- namespace unset when not set — no namespace configured, error
Phase C: Data Migration — filesystem only (commit 3)
File: tests/cli/datastore/namespace/migrate_test.ts
No cloud credentials needed.
- Forward migration round-trip — solo repo with data, namespace set, verify orphaned (total: 0), migrate --confirm, data list shows all data with namespace
- Data integrity — diff pre/post migration JSON (minus namespace field), identical
- Dry-run accuracy — migrate without --confirm shows preview, no files moved, then confirm, preview matched
- Reverse migration round-trip — after forward, unset --migrate --confirm, data list byte-identical to pre-migration
- Conflict on reverse — forward migrate, create file at un-namespaced path, reverse refuses
- Data written after set before migrate — new data goes to namespaced path, old orphaned, migrate brings both together
Phase D: Namespace-Scoped Sync — S3/GCS required (commit 4)
File: tests/cli/datastore/namespace/sync_test.ts
Uses getTestBackends() against ministack (S3) and fake-gcs-server (GCS). This is the critical phase — the PR #1386 failure mode lives here.
- THE KILL SWITCH — Repo A (ns: infra) writes + syncs. Repo B (ns: security) writes + syncs. Repo B wipes cache, pulls. data list returns own data. If this returns 0 rows, catalog rebuild didnt fire after pull.
- Namespace isolation in bucket — listObjects shows infra/ and security/ prefixes only, no un-namespaced keys
- Per-namespace index — infra/.datastore-index.json and security/.datastore-index.json exist, no global
- Cross-namespace deletion safety — Repo A pushes, Repo B pushes, Repo A pushes again, Repo B keys untouched
- Namespace fast-path — push twice with no changes, second is no-op (sidecar works per-namespace)
- Foreign catalog export/pull — Repo A exports catalog, Repo B pulls foreign catalog, Repo B queries ns == infra, sees metadata
- Solo mode regression — solo repo against same backend, push/pull/data list unchanged
Phase E: CEL Cross-Namespace Queries — filesystem only (commit 5)
File: tests/cli/data/namespace/query_test.ts
No cloud credentials needed.
- Own-namespace default — namespaced repo, data.latest returns own namespace only
- Cross-namespace ns:model — two namespaces seeded, data query ns == security returns foreign data
- Wildcard ambiguity — same model name in two namespaces, wildcard errors
- Wildcard unambiguous — model in one namespace, wildcard succeeds
- data.query() spans all — no ns predicate returns all namespaces
- NAMESPACE column in output — namespaced repo shows column
- Solo mode no column — solo repo hides column
- ns == empty string in solo — returns all data
Phase F: Feature Interactions — mostly filesystem (commit 6)
File: tests/cli/e2e/namespace_features_test.ts
- Workflow in namespaced mode — multi-step workflow, all data carries correct namespace
- Data GC in namespaced mode — only own namespace cleaned, foreign rows untouched
- Data delete in namespaced mode — only own namespace affected
- Datastore compact — works with namespaced catalog
- Lock status — shows per-namespace lock key
- Extension model in namespaced mode — test fixture model writes data with correct namespace
Phase G: Adversarial + Regressions (commit 7)
File: tests/cli/adversarial/namespace_isolation_test.ts File: tests/cli/datastore/namespace/regressions_test.ts
Named regression tests for specific bugs found during implementation: 39. PR #1386 regression — explicit: writer pushes, reader pulls, catalog rebuilt, data visible 40. Bug #534 regression — explicit sync --push in namespaced mode lands data under ns/ prefix 41. Bug #536 regression — datastore setup extension in namespaced mode respects namespace
Adversarial scenarios: 42. Concurrent namespace push — two repos different namespaces push simultaneously via runConcurrent, no corruption 43. Corrupt .namespace.json — invalid JSON in manifest, namespaces list handles gracefully 44. Missing manifest but namespace configured — system works without conflict detection 45. Half-migrated state — some dirs moved, migrate handles remaining or errors clearly 46. Catalog rebuild preserves own data loses foreign — delete catalog, backfill, own data survives, catalog pull restores foreign
Upgrade path: 47. Solo to namespace upgrade journey — solo repo with data, upgrade binary, data list identical, namespace set, migrate, push to S3, pull from fresh cache, data visible
Phase H: User Journeys — S3/GCS required (commit 8)
File: tests/cli/e2e/namespace_journey_test.ts
Full end-to-end user journeys against real (local) S3/GCS:
- Two teams one bucket — Repo A (infra) and Repo B (security) against same bucket. Both write, push, pull foreign catalogs, query cross-namespace. Complete isolation + visibility.
- Solo repo joins a giga-swamp — existing solo repo with data, namespace set, migrate, push to shared bucket where another namespaced repo has data. Both visible.
- Leave the giga-swamp — namespaced repo, unset --migrate, back to solo. New datastore. Push. Data intact.
Rollout Order
A (helpers) ships first, unblocks everything. B + C + E can develop in parallel (filesystem only, no cloud deps). D is the critical cloud phase — gates on ministack + fake-gcs-server. F builds on B. G builds on D. H builds on everything.
Ship order: A, B, C, E, D, F, G, H — each as its own commit.
Verification
Each phase must pass deno test locally before committing. The full suite runs in CI against ministack and fake-gcs-server containers. Cloud-dependent tests (D, G adversarial, H) skip gracefully when containers are unavailable.
Closed
No activity in this phase yet.