Gitlab Datastore
Stores swamp runtime data in GitLab using the Terraform state HTTP API. Provides distributed locking via GitLab's native state locking mechanism and bidirectional sync between local cache and GitLab. Emits OpenTelemetry spans for GitLab API calls, lock acquisition/release, and push/pull sync so datastore activity is visible in traces when a TracerProvider is configured. The access token is read from a swamp vault with a vault.get expression in the datastore config, never written literally.
2026.10.21.1
Fixed: The README configuration example put a literal personal access token
into .swamp.yaml. It now stores the token with swamp vault put gitlab TOKEN
and references it with "${{ vault.get(gitlab, TOKEN) }}", and shows the same
expression inside SWAMP_DATASTORE.
Changed: The token config field is marked sensitive and its description
says to supply it with a vault.get expression.
Upgrade note: No behavioral or schema changes. If your .swamp.yaml holds a
literal token, move it into a vault and replace it with a vault.get
expression.
Stores swamp runtime data in GitLab using the Terraform state HTTP API with native locking support
Config Fields
| Field | Type | Description |
|---|---|---|
| projectId | string | GitLab project ID (numeric) or URL-encoded path (e.g., 'mygroup/myproject') |
| baseUrl? | string | GitLab instance URL |
| token | string | GitLab personal access token or CI job token with api scope. Supply it |
| username? | string | GitLab username (optional, defaults to 'gitlab-ci-token') |
| statePrefix? | string | Prefix for state names to namespace swamp data |
2026.10.14.1
Added: The manifest now declares license: Apache-2.0 (SPDX). The registry quality rubric scores a declared license separately from the bundled LICENSE.md.
Upgrade note: Metadata only. No code, schema, or behavioral changes.
2026.09.18.1
Upgrade note: Normalized npm:zod dependency version to 4.6.5 across the
repo. No behavioral changes in this extension.
2026.09.18.1
Upgrade note: Normalized npm:zod dependency version to 4.6.5 across the
repo. No behavioral changes in this extension.
2026.09.15.1
Changed: Bump zod 4.4.3 → 4.6.5
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.28.1
Changed: Normalized the extension license to Apache-2.0 and corrected the copyright holder to "Sean Escriva". Extensions that previously shipped an MIT LICENSE.md are now Apache-2.0, consistent with the repository root and every other extension. No code or behavioral changes.
Upgrade note: License text only. No API, schema, or runtime behavior changed.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.26.3
Fixed: Restored inline npm:zod@4.4.3 import specifiers so the registry
quality scorer can resolve dependencies and score the extension. An earlier
release used a bare "zod" import-map specifier, which published but scored as
unscored.
Changed: Retained explicit compilerOptions.strict in deno.json. No
behavioral or schema changes.
2026.08.26.1
Changed: Normalized deno.json configuration for repo-wide consistency:
added explicit compilerOptions.strict and migrated zod dependency to the
import map (bare "zod" specifier instead of inline npm:zod@4.4.3). No
behavioral changes — runtime resolution is identical.
2026.08.25.1
Changed: Updated labels for improved extension discoverability. Added cross-cutting category labels (security, observability, finops, infrastructure, networking, compliance, devops, ai, incident-response) where applicable.
updated labels
2026.08.24.1
Added Troubleshooting section documenting the 60s stale-lock threshold, 4MB file size cap, 429 retry budget, first-push slow path, state path encoding, and projectId format options.
2026.08.21.2
Changed: GitLab API failures now name the operation and the state they were acting on. Errors from getState, putState, deleteState, lock, and unlock used to report only an HTTP status (e.g. "GitLab API error: 500 Internal Server Error"); they now say which state and which operation failed. listStates previously swallowed project-lookup and GraphQL failures by silently returning an empty list — a transient 500 during push looked identical to "no remote states," which could cause pushChanged to tombstone (delete) every locally-tracked file as if it no longer existed remotely. It now throws instead, so a listing failure surfaces as an error rather than as data loss. getLockInfo likewise used to treat any non-2xx/404/204 response as "not locked"; it now throws on unexpected failures rather than letting acquire() or forceRelease() proceed as though a lock were free when the server call actually failed.
2026.08.21.1
Changed: Tightened projectId and token in the datastore config schema to require non-empty strings. Both are required identifiers that the GitLab API would never accept blank, so this only rejects configs that were already broken.
2026.08.20.1
Upgrade note: Pinned zod to exact version 4.4.3 (was unpinned range npm:zod@4). No behavioral changes — dependency version alignment only.
2026.08.21.1
Changed: Tightened projectId and token in the datastore config schema to require non-empty strings. Both are required identifiers that the GitLab API would never accept blank, so this only rejects configs that were already broken.
2026.08.20.1
Upgrade note: Pinned zod to exact version 4.4.3 (was unpinned range npm:zod@4). No behavioral changes — dependency version alignment only.
2026.08.20.1
Upgrade note: Pinned zod to exact version 4.4.3 (was unpinned range npm:zod@4). No behavioral changes — dependency version alignment only.
2026.08.03.1
Fixed: GitLab API calls that hit a 429 Too Many Requests response failed
immediately instead of retrying, which reliably interrupted swamp datastore setup migrations that push hundreds of state objects in quick succession. All
GitLab API calls now retry a 429 up to 5 times before giving up: the client
honors a Retry-After header when GitLab sends one, and otherwise backs off
exponentially (1s, 2s, 4s, ...), with jitter either way so many concurrent
callers don't retry in lockstep. Only after exhausting retries does the call
fail with the same error message as before.
Changed: Lock acquisition (GitLabLock.acquire()) bounds every retry —
including 429 backoff — to what's left of its own maxWaitMs, so a lock call
gives up on schedule even while GitLab is actively rate-limiting it, instead
of a single retrying call silently running past the caller's declared
timeout. deleteState calls made during push (tombstoning) now honor the
sync operation's cancellation signal the same way getState/putState
already did. Lock release, force-release, inspection, and the health-check
verifier are not bounded this way — they have no existing timeout contract to
preserve, so a sustained 429 there can still take up to the full retry
backoff (worst case, a large Retry-After × 5 attempts) before failing.
Upgrade note: No action required. Retries are internal to the GitLab API client and transparent to almost every caller — no config or call-site changes.
2026.07.30.1
Fixed: Lock contention on back-to-back model operations. Previously all
lock paths mapped to a single GitLab Terraform state ({prefix}--lock),
causing the second operation's sync push to time out waiting for the first to
release. Locks are now per-path — each datastore path gets its own lock state
({prefix}--lock--{sanitized-path}), eliminating cross-model contention
entirely.
Changed: Default lock timing constants tuned for actual push latency.
TTL reduced from 30s to 10s, retry interval from 1s to 500ms, max wait from
60s to 30s. Stale lock detection remains at 60s (threshold is now configurable
via staleLockThresholdMs and decoupled from TTL).
Upgrade note: Existing locks held under the old single-state name
({prefix}--lock) will not conflict with the new per-path names. No migration
required — old lock states become orphaned and can be cleaned up via the GitLab
Terraform states UI if desired.
2026.07.25.1
Added: OpenTelemetry spans for every layer of the datastore. All GitLab API
calls now flow through a single choke point that emits one span each
(GitLab getState, GitLab putState, GitLab lock, GitLab listStates, …)
carrying the HTTP method, response status, project ID, state name, and server
host. The lock emits gitlab-datastore lock acquire / release / withLock /
inspect / forceRelease, with acquire recording wait duration, contention,
and the current holder. The sync service emits
gitlab-datastore pullChanged / pushChanged / hydrateFile / preparePush /
commitPush with file counts and the number of states listed.
Added: Lock retries are recorded as retry span events, distinguishing
ordinary contention from stealing a stale lock.
Changed: Non-2xx responses now mark their span as an error, except where a
status is normal control flow — a 404 from getState meaning the state does
not exist, a 409 or 423 from lock meaning another holder has it, a 404 or 204
from getLockInfo meaning the state is unlocked. This client inspects
response.status by hand rather than throwing, so without the distinction a
span would have reported success on a 500.
Changed: pushChanged records datastore.dirty_path_mode rather than
datastore.fast_path_hit. In the sibling extensions fast_path_hit means no
work was done; this method has no short-circuit, and a dirty-path push that
uploads files is not a fast path.
Changed: Nothing observable without tracing configured. The extension
depends on @opentelemetry/api only; the host process owns the
TracerProvider, and every span is a no-op when none is registered. Existing
behaviour, return values, and log output are unchanged.
Note on secrets: Every request carries a PRIVATE-TOKEN header and request bodies carry file content. Neither headers nor bodies are ever recorded — span attributes hold only the operation name, HTTP method, status, project ID, state name, and host. A test asserts no span attribute contains the token.
updated labels
2026.07.18.1
Changed: Version bump only, no code changes.
2026.07.16.1
Changed: Internal-only version bump. PR #183 added a fmt:check task to deno.json, which is not part of the published bundle — this release's published content is identical to 2026.06.30.1.
2026.07.16.1
Changed: Internal-only version bump. PR #183 added a fmt:check task to deno.json, which is not part of the published bundle — this release's published content is identical to 2026.06.30.1.
2026.06.30.1
Two-Phase Datastore Sync
Adds preparePush/commitPush two-phase sync protocol alongside the existing
pushChanged single-shot path.
Added
TwoPhaseSyncServiceinterface withpreparePush()andcommitPush()methodsPushManifestopaque branded type for safe manifest passing between phasespreparePush()collects diff outside the lock (no remote writes)commitPush()re-reads fresh state under lock, uploads entries, merges hashescapabilities()now reportstwoPhaseSync: true
Changed
- Extracted
EXCLUDED_DIRSandisExcludedFileto module-level constants (deduplication) - No breaking changes to existing
pushChangedbehavior
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License2/2earned
- Verified public repository2/2earned