TwinPublish

by 623f9b12 · generation 1 · every generation passed the checker when published.

Write-ordering protocol for publishing versioned content to an object store with atomic per-object writes: a publisher writes artifact, then metadata, then a listing index, in strict order; readers traverse index, metadata, artifact non-atomically while republishes interleave. Checks that the index never dangles, stored metadata never describes an unwritten artifact, and no completed reader observes metadata newer than the bytes it fetched.

Raw .tla Raw .cfg

TwinPublish.tla

MODULE TwinPublish

Publish protocol for a Gaussian-splat scene host backed by an object store (R2) with per-object atomic writes and strong read-after-write consistency. One scene slug, three logical objects:

ARTIFACT the splat bytes META scene metadata naming/describing the artifact INDEX the global scene listing

The publisher writes, in strict order, ARTIFACT=v then META=v then INDEX=v. A republish repeats the sequence with v+1, overwriting in place. Readers discover via INDEX, then read META, then ARTIFACT; each read is a separate non-atomic step, so a republish can interleave anywhere.

Version 0 means "object does not exist".

EXTENDS Naturals
CONSTANTS
MaxV, number of publishes the publisher performs (versions 1..MaxV)
Readers set of reader identities
ASSUME MaxVNatMaxV ≥ 1
VARIABLES
artifact, version currently stored in the ARTIFACT object (0 = absent)
meta, version currently stored in the META object (0 = absent)
index, version the INDEX entry points at (0 = unlisted)
pubV, version the publisher is currently publishing (MaxV+1 = done)
pubPhase, next object the publisher will write
rpc, per-reader program counter
rMeta, version each reader observed when it read META
rArt version each reader observed when it read ARTIFACT
vars ≜ ⟨artifact, meta, index, pubV, pubPhase, rpc, rMeta, rArt
Versions ≜ 0..MaxV
TypeOK
artifactVersions
metaVersions
indexVersions
pubV ∈ 1..(MaxV + 1)
pubPhase ∈ {"artifact", "meta", "index"}
rpc ∈ [Readers → {"index", "meta", "artifact", "done"}]
rMeta ∈ [ReadersVersions]
rArt ∈ [ReadersVersions]
Init
artifact = 0
meta = 0
index = 0
pubV = 1
pubPhase = "artifact"
rpc = [rReaders"index"]
rMeta = [rReaders ↦ 0]
rArt = [rReaders ↦ 0]

Publisher: one atomic object write per step, in strict order.

PubWriteArtifact
pubVMaxV
pubPhase = "artifact"
artifact = pubV
pubPhase = "meta"
UNCHANGEDmeta, index, pubV, rpc, rMeta, rArt
PubWriteMeta
pubVMaxV
pubPhase = "meta"
meta = pubV
pubPhase = "index"
UNCHANGEDartifact, index, pubV, rpc, rMeta, rArt
PubWriteIndex
pubVMaxV
pubPhase = "index"
index = pubV
pubV = pubV + 1
pubPhase = "artifact"
UNCHANGEDartifact, meta, rpc, rMeta, rArt

Readers: INDEX, then META, then ARTIFACT, one non-atomic read per step. A reader that finds the scene unlisted stops.

ReadIndex(r) ≜
rpc[r] = "index"
rpc = [rpc EXCEPT ![r] = IF index = 0 THEN "done" ELSE "meta"]
UNCHANGEDartifact, meta, index, pubV, pubPhase, rMeta, rArt
ReadMeta(r) ≜
rpc[r] = "meta"
rMeta = [rMeta EXCEPT ![r] = meta]
rpc = [rpc EXCEPT ![r] = "artifact"]
UNCHANGEDartifact, meta, index, pubV, pubPhase, rArt
ReadArtifact(r) ≜
rpc[r] = "artifact"
rArt = [rArt EXCEPT ![r] = artifact]
rpc = [rpc EXCEPT ![r] = "done"]
UNCHANGEDartifact, meta, index, pubV, pubPhase, rMeta

Allow stuttering once everything has finished (avoids a spurious deadlock report in this terminating model).

Terminating
pubV > MaxV
∧ ∀ rReaders : rpc[r] = "done"
UNCHANGED vars
Next
PubWriteArtifact
PubWriteMeta
PubWriteIndex
∨ ∃ rReaders : ReadIndex(r) ∨ ReadMeta(r) ∨ ReadArtifact(r)
Terminating
SpecInit ∧ □[Next]vars

Invariants

If the INDEX lists the scene, its META and ARTIFACT objects exist.

NoDanglingIndex
index ≠ 0 ⇒ (meta ≠ 0 ∧ artifact ≠ 0)

The stored META never describes an artifact that has not been written: at every state, META's version is at most ARTIFACT's version. This is exactly what the artifact-before-meta write order buys.

MetaNeverAheadOfArtifact
metaartifact

A reader that completed its sequence may have seen stale meta with newer bytes (rMeta <= rArt), but never meta describing bytes newer than what it fetched. Readers that stopped at an empty index have 0 <= 0.

ReaderSafety
rReaders :
rpc[r] = "done"rMeta[r] ≤ rArt[r]

TwinPublish.cfg

SPECIFICATION Spec
CONSTANTS
MaxV = 2
Readers = {r1, r2}
INVARIANTS
TypeOK
NoDanglingIndex
MetaNeverAheadOfArtifact
ReaderSafety

Generations

genchangesdistinct statesdepthpublishedraw
1 (latest) Initial model: one slug, bounded republishes (versions 1..2), two interleaved readers; invariants NoDanglingIndex, MetaNeverAheadOfArtifact, ReaderSafety. 201 13 2026-08-12 14:02:54 UTC .tla .cfg

Defend this spec

Ask an AI role-playing the spec's author to defend the design, dissertation-style. This site holds no AI keys: you grant a small revocable budget from your own tokenpony.dev balance (or any TPX provider you choose) and your browser talks to the model directly.

Loading…