TramAI - governed AI workflows for Java and Kotlin

Adding a Store

A store implementation (approval, continuation, credential, audit, suspended invocation, checkpoint, lease, chat memory) implements an authoritative store SPI in its owning module and, where a shared TCK exists, proves conformance by enrolling in it. Enrolment is architecture-enforced: a store without its TCK runner fails the build. The TCK is the contract.

Pick The Owning Module First

Store familySPI lives in
Approval, approval continuation, resume credentialtramai-core (dev.tramai/core/approval)
Audittramai-security
Suspended invocationtramai-engine
Workflow checkpoint, workflow leasetramai-orchestration

Implementations land in the persistence module that will own the backend (tramai-persistence-file, tramai-persistence-jdbc), or in the module that owns the new backend. In-memory defaults stay in the module that owns the SPI. Shared TCK fixtures live in tramai-testing.

The Extension Points

StoreSPI fileRequired methods
ApprovalStoretramai-core/.../approval/ApprovalStore.ktcreate · get · transition(approvalId, expectedVersion, transition) · consumeApprovedOrReplay
ApprovalContinuationStoretramai-core/.../approval/ApprovalContinuationStore.ktcreate(continuation, arguments) · get · claimForExecution · complete · expire · cancel · findStaleClaimed · forceCancelClaimed · sweepExpired
ApprovalResumeCredentialStoretramai-core/.../approval/gateway/ApprovalResumeCredentialStore.ktcreate (duplicate → IllegalStateException) · get · delete; must encrypt the sealed resume token at rest
AuditStoretramai-security/.../audit/AuditStore.ktappendNext · readStream · readStreamPage · latestEvent
SuspendedInvocationStoretramai-engine/.../SuspendedInvocationStore.ktcreate(metadata, replayEnvelope) · get · revealReplayEnvelope · remove
WorkflowCheckpointStoretramai-orchestration/.../WorkflowPersistence.ktload · save(checkpoint, expectedRevision) · delete · requireRecovery · clearRecovery, plus WorkflowStateCodec<S>
WorkflowLeaseStoretramai-orchestration/.../WorkflowLease.ktcurrentLease · claim · renew · release, plus optional WorkflowLeaseCheckpointFence

Two hard invariants cut across all of them:

  • claimForExecution is the only path that exposes raw continuation arguments. They are released exactly once.
  • revealReplayEnvelope is the only path that exposes the replay envelope, and only after a claim. get() must not return it.

Mandatory Contract Tests

Shared TCKs live in tramai-testing/src/testFixtures/kotlin/dev/tramai/testing/persistence/:

Store familyShared TCKHarness / enrolment
ApprovalStoreapproval/ApprovalStoreTck.ktapproval/ApprovalStoreTckHarness.kt (createStore(clock: MutableClock), closeStore)
ApprovalContinuationStoreapproval/continuation/ApprovalContinuationStoreTck.ktapproval/continuation/ApprovalContinuationStoreTckHarness.kt
AuditStoreaudit/AuditStoreTck.ktdirect createStore()
SuspendedInvocationStoreengine/SuspendedInvocationStoreTck.ktdirect
WorkflowCheckpointStorecheckpoint/WorkflowCheckpointStoreTck.ktdirect createStore()
WorkflowLeaseStorelease/WorkflowLeaseStoreTck.ktdirect createStore(clock: MutableMillisClock)
WorkflowLeaseCheckpointFencelease/WorkflowLeaseCheckpointFenceTck.ktdirect
ChatMemoryStorememory/ChatMemoryStoreTck.ktmemory/ChatMemoryStoreTckHarness.kt
SovereignOpsAuditOutboxStoreoutbox/SovereignOpsAuditOutboxStoreTck.ktSovereignOpsAuditOutboxStoreTckEnrollmentArchitectureTest with in-memory/file/JDBC runners

The approval TCKs are the deepest of the set. ApprovalStoreTck pins the transition matrix, consumption and exact-replay semantics, a model-based property check, a wrong-version matrix, and a race schedule. ApprovalContinuationStoreTck pins claim, exactly-once argument release, expiry, cancel, complete, recovery, sweep, races, and a continuation version ceiling of version ≤ 2 per continuation.

Stores without a shared TCK

Do not search for a TCK that does not exist. These are covered by module-level contract tests only:

StoreObligation
ApprovalResumeCredentialStoreSPI conformance, encryption of the sealed resume token at rest, and module tests (for example JdbcApprovalResumeCredentialStoreTest.kt under tramai-spring-boot-starter-sovereign-persistence-jdbc/src/test/)
SovereignOpsWorkerLeaseStoreSPI conformance plus module tests
SovereignOpsApprovalMutationStore, SovereignOpsApprovalRequestMutationStore, ApprovedContinuationResumeQueueStatusStore, ApprovedContinuationResumeWorkerStatusStoreStarter-local stores with no engine SPI; module tests only

Verify per store before assuming either situation.

Enrolment Is Architecture-Enforced

tramai-testing/src/test/kotlin/dev/tramai/testing/StoreEnrollmentScanner.kt backs one *EnrollmentArchitectureTest per store family:

  • ApprovalStoreTckEnrollmentArchitectureTest
  • ApprovalContinuationStoreTckEnrollmentArchitectureTest
  • AuditStoreTckEnrollmentArchitectureTest
  • SuspendedInvocationStoreTckEnrollmentArchitectureTest
  • WorkflowCheckpointStoreTckEnrollmentArchitectureTest
  • WorkflowLeaseStoreTckEnrollmentArchitectureTest
  • WorkflowLeaseCheckpointFenceTckEnrollmentArchitectureTest
  • ChatMemoryStoreTckEnrollmentArchitectureTest
  • SovereignOpsAuditOutboxStoreTckEnrollmentArchitectureTest

Each pins a runner allowlist and requires every concrete implementation to have a valid runner.

Files That Change

  • The new store class and its <Store>TckTest.kt runner, in the owning module's src/test/kotlin.
  • JDBC codecs, when the backend is JDBC: JdbcReplayEnvelopeCodec.kt, JdbcAuditPayloadCodec.kt, the continuation-arguments codec inside JdbcApprovalContinuationStore.kt, and JdbcOpsAuditOutboxPayloadCodec.kt.
  • Spring wiring, when the store is auto-configurable: a @ConditionalOnMissingBean bean in SovereignJdbcPersistenceAutoConfiguration.kt or SovereignFilePersistenceAutoConfiguration.kt, plus the auto-configuration .imports registration.
  • config/quality/module-catalog.yml, if the store ships in a new module.

@ConditionalOnMissingBean is mandatory. A user-provided store bean must always win over a new default.

Replay-Envelope Security

SensitiveReplayEnvelope is opaque — toString returns [REDACTED] and only revealForResume() exposes the contents. A store must:

  1. verify metadata.replayEnvelopeDigest at create time, and
  2. re-validate the replay invariants before persisting.

ReplayEnvelopeValidator and ReplayEnvelopeDigestHelper are shared: stores re-validate, they never re-define. Encryption at rest is the implementer's seam — key management, algorithm, and nonce are owned by the store.

Verification

./gradlew :tramai-testing:test                        # all *EnrollmentArchitectureTest gates
./gradlew :<module>:test --tests '*TckTest'           # your store's TCK runner
./gradlew verifyPr
./gradlew verifyChangePolicy -PchangeClass=runtime-behaviour

JDBC TCK runners require Docker (Testcontainers), because they assert against real PostgreSQL durability behaviour.

What Not To Change In This Pull Request

  • The TCKs. They are independent oracles; never weaken them to make a store pass.
  • The SPI contracts. ApprovalStore, ApprovalContinuationStore, and friends change only for a public-api-classified change, never when adding an implementation.
  • config/quality/0.6.0-baseline.json. Never edited in the same pull request.

Common Mistakes

SymptomCause
verifyPr fails in :tramai-testing:testThe runner extends nothing, or extends a copied TCK instead of the real one
Replay trust brokenDigest not verified at create, or the raw envelope exposed via get()
Durable state mutated by a rejected actionOptimistic concurrency ignored — rejected actions must not mutate durable state
User beans silently replacedA new Spring store bean without @ConditionalOnMissingBean
Cancellation tests failCancellationException caught, wrapped, or persisted instead of rethrown
TCK assertions fail on versionA new state increments the continuation version past the version ≤ 2 ceiling