Relationships
#3102 Replayable state fragments: Git-reviewable desired state for resources changed by model methods
Opened by keeb · 10/6/2026
Problem
A model method run that changes a real resource is an event, not state. Swamp records excellent evidence that something happened (run records, triggeredBy, versioned and checksummed data), but nothing reviewable declares that the resource should exist.
Teams that hold production state in Git (Terraform for what a provider covers, scripts plus JSON for what it doesn't) hit this as soon as they let people change those systems through self-service Swamp workflows:
- No provable state. You can't point at Git and say "production matches this" for anything created through Swamp.
- No rebuild. If a resource is deleted or changed in the vendor UI, or you need a fresh workspace, there's nothing to replay.
- A third source of truth. Terraform, JSON and Swamp run history each own a slice of the same system.
The obvious question from a reviewer is: "if people can change X through Swamp, where does that change live as code?" Generating Terraform/JSON from Swamp doesn't answer it well. It hands execution to a weaker executor, puts the resource logic in two places, and makes Swamp's audit log describe a proposal rather than the change.
Proposed: state fragments
A state-changing method (create/update kinds) emits a state fragment: a deterministic artifact recording desired state, not the command log. Replaying raw API calls would create a duplicate, not confirm the original. Five blocks:
| Block | Holds | Used for |
|---|---|---|
metadata |
model type + version, method, name, dependsOn |
ordering, schema checks |
intent |
resolved inputs defining the resource (secrets as vault refs, never values) | the replay input |
identity |
provider ID returned by the run + a natural key | finding the resource again |
observed |
hash of provider-reported state + pointer to the versioned data | drift detection |
provenance |
workflow run ID, triggeredBy, swamp version, repo commit | audit |
apiVersion: swamp/v1alpha1
kind: StateFragment
metadata:
name: audience-high-value-customers
modelType: "@example/cdp/audience"
modelTypeVersion: "2026.08.07.1"
method: create
dependsOn: [source-web-prod]
intent:
spaceId: spa_...
name: High value customers
definition:
query: "event('Order Completed').count() >= 5"
enabled: true
identity:
provider: cdp
resourceType: audience
id: aud_...
naturalKey: [spaceId, name]
observed:
hash: sha256:...
dataRef: data/cdp-audience/high-value-customers@v7
provenance:
workflowRun: 01J9...
triggeredBy: jane.doe
swampVersion: 20261001.070022.0
repoCommit: 4e1b2c9Replay is reconcile, not re-run:
plan(read-only, for PR checks and scheduled drift detection) andapply.- Look up by ID, then by natural key. If it's missing, create it and record the new ID. If it matches intent, no-op and mark it verified. If it has drifted, reapply intent. Then write back identity, observed hash and provenance.
- Deletion is an explicit tombstone/prune. A fragment disappearing must be reported as an orphan, never trigger an implicit delete.
Acceptance criteria:
- Running the same workflow twice with the same inputs: the second run is a no-op, and intent and identity are unchanged.
- Deleting a resource in the vendor UI:
planreports it missing, andapplyrecreates it and updates identity. - An empty workspace plus a directory of fragments rebuilds in dependency order.
- No fragment ever contains a secret value.
- A fragment change reads cleanly as a PR diff.
What exists today vs. what needs core
Much of this is already present:
- Git-tracked YAML definitions.
sensitivefield meta and vault refs.- Immutable, checksummed data versions with
ownerDefinition. ExecutionProvenance(triggeredBy,workflowRunId,definitionHash,modelVersion).- Method kinds and delete tombstones.
- Per-model locks.
- Workflow
dependsOnand CEL dependency extraction. VersionUpgradechains.context.runModelfor a wrapper reconciler.- Method-scoped reports that could emit a fragment.
So a single model family can approximate this as an extension today. What can only come from core, so fragments are uniform across extensions:
- Field roles in the type schema. Mark intent / identity / provider-generated (like
sensitivetoday), so emission is consistent across extensions without each author inventing it. - Deterministic serialisation. Stable key order, volatile fields (UUIDs,
createdAt, version numbers) kept out ofintent, so diffs show only real changes. - Provenance completeness. Swamp CLI version and repo commit are not in
ExecutionProvenanceSchematoday. - Guaranteed secret handling. Method-scoped reports receive resolved args, so redaction / vault-ref rewriting must be enforced by core, not trusted to each emitter.
- Build on versioned data rather than duplicating it.
observedshould be a hash plus a pointer into data Swamp already keeps.
Where plan/apply run is open. A trusted controller outside the workflow runtime (driving model get/create/edit --server) is a reasonable answer and keeps workflows as the execution plane.
Open design questions
- Where does the artifact live? Options: Swamp opens a PR against the repo (Git/GitHub integration plus a file-handling surface), the datastore is the record with an export/sync to Git, or a report writes files that CI commits. If it's Git, a PR is preferable to a direct commit so self-service changes still get human review.
- Locking. What stops a workflow and a replay changing the same resource at once? Is the per-model lock sufficient across fragments?
- Ordering. Should
dependsOnbe explicit, or derived from CEL references between models? - Upgrades. When a model type version changes, how are existing fragments migrated? Reuse
VersionUpgrade? - Large or sensitive payloads. Is hash plus pointer enough in
observed, or should the full response be optionally embedded?
Suggested phasing
- Decide where artifacts live.
- Emit: first-class fragment output plus the schema field roles.
- Plan: read-only plan across a directory of fragments, on PRs and on a schedule.
- Apply: create missing, reapply drifted, explicit prune for tombstones.
- Import: generate fragments from existing Terraform state / JSON definitions, and migrate resource types one at a time with one owner per resource.
Alternatives
- Swamp generates Terraform/JSON for existing pipelines. Rejected for the reasons above: weaker executor, duplicated logic, and execution-time evidence is lost.
- Per-extension convention only (a report emitter plus a wrapper reconciler model). Works today for one family, but fragments diverge across extensions, and provenance/secret guarantees can't be enforced.
Related
- #1929,
swamp model apply --from-dir: GitOps for model definitions. This is the resource-state counterpart. - #1566, crash-safe atomic or reconcilable multi-resource writes.
Impact
This lets Git hold a system's state through Swamp alone, with Swamp as both writer and executor. Teams can retire Terraform-plus-scripts splits for systems with weak providers, instead of adding Swamp as a third source of truth. Terraform keeps an edge (mature plan, state locking, drift detection) until Swamp grows these equivalents.
Open
No activity in this phase yet.
Sign in to post a ripple.