Specification §9

Semantics and algorithms.

Companion to §9 of the specification. The key words MUST, MUST NOT, SHOULD, and MAY are to be interpreted as described in RFC 2119. The contract in draft form: memory-contract.html.

FrogNet Specification — Draft 0.9, revision 2026-08-29. Cite a conformance claim against this revision, not against a section number alone.

1. Model

FrogNet Memory is a single addressable store per connected pond, held by the elected database host. A value is addressed by a three-coordinate tuple key. A write stores the value at its key; a read returns the current value, its timestamp, or absence. For operational state, last write wins. The store provides no consensus, no cross-value atomicity, no history, and no total order beyond the serialization order of the single store.

2. Addressing

A tuple key is three application-chosen coordinates. The store imposes no schema and requires no registration; a key exists when it is first written. Coordinate names sort, so hierarchy is a naming convention, not a store feature.

UseConvention
Program stateprogram / variable / user
Machine globalshost / globals / variable
Sensor fleetsensor name / type / location

A query MAY specify all three coordinates (one item), any two (a set), or any one (a larger set). A prefix query on a name is the enumeration primitive; the FrogNet monitor is implemented entirely as such reads.

The three-coordinate key
The three-coordinate key

The third coordinate is a scope, and scopes are constructed rather than free-form:

ScopeFormFor
hosthost:<ip>:<pid>A long-running service whose tuple should be reaped when that instance stops
nodehost:<ip>A periodic refresh writer whose tuple must outlive any single process
rolehost:<ip>:<role>Capability tuples. A node advertising two roles MUST qualify by role, or one row's role flips as each write overwrites the other.
sessionsession:<id>Per-call state, one scope per concern

At the store, the address is the unique key Name + Type + Address [TUPLE_ADDRESS_V1]; all three MUST be present in every write, empty where the caller supplies nothing, because the conflict clause that makes the write an upsert only fires when the whole key is in the statement.

3. Write semantics

A write MUST be a single atomic upsert against the unique tuple key (INSERT ... ON DUPLICATE KEY UPDATE): resolve-or-create in one statement. A write MUST NOT require a prior read to resolve or create its tuple. Two concurrent writers are ordered by the single serialization point; the later write wins. This guarantee covers the write only: an application that reads a value and derives the next one from it has constructed a read-modify-write cycle and owns its races.

Single-writer discipline is the operative convention: each key has one writer by construction — a node writes its own state. Cross-key composition (two values moving as one) is not provided; where an order across values is required, the decision MUST be routed through an elected role rather than inferred from the store.

4. Read semantics and freshness

A read MUST NOT block on any remote condition. It returns the current value with its timestamp, or absence. A value outside the freshness window MUST be treated as absent, not as wrong. There is no change notification; observation is by polling. What the store provides is bounded-stale current state: a read observes some real moment, timestamped.

The store decides freshness, not the reader [ENVELOPE_TS_V1]. A freshness-bounded read passes its window to the store, which filters on the row's own update time — on the clock that stamped it. A reader MUST NOT compare its own clock against a timestamp inside the payload.

That was the earlier behaviour and it failed exactly as skew predicts: a node whose clock ran two minutes slow disappeared from every roster while publishing correctly, and the only symptom was an empty membership list. One clock decides and it is the one that wrote the row, so skew between nodes cannot matter.

Freshness is envelope information. A payload MAY carry a timestamp of its own and it MUST NOT be consulted for this purpose; where a caller reports an age, the age reported MUST be the store's number.

A row whose freshness cannot be established is not fresh. Where a bounded read returns a row carrying no update envelope, the reader MUST drop it and MUST say so. A store that does not implement the filter returns everything, and passing those rows through presents a full history as though it were current: departed peers in the roster, months-old sessions offered as joinable.

5. Coherence and the database-host election

Spec: §8 · Croakus: §15, §16

Coherence is structural: one database per network is one serialization point is one order. The election preserves that invariant when membership changes.

on membership change (join, split, merge, loss of host):
  1. each node publishes its capability row -- cores, memory,
     disk class, current roles -- keyed by its own identity
  2. each node reads every capability row
  3. each node runs the same scoring function over the same rows
  4. each node arrives at the same winner independently
  5. the winner assumes databasehost.frognet; every other node
     resolves that name to the winner

No votes are exchanged and no announcement is made; determinism over shared inputs replaces coordination. A node MUST rebind databasehost.frognet whenever its derived winner changes. Control state for the role lives in a separate plane (databasehost_control) so the role can move without the data plane renaming anything.

Capability scoring — deterministic
Capability scoring — deterministic
databasehost against databasehost_control
databasehost against databasehost_control

6. Partition and merge

Spec: §13 · Croakus: §18, §29

A partition is a defined state. Each fragment holds an election where the host was lost and continues with the members it retains. Writes from a fragment that cannot reach its store are absent, not queued. On heal there is no merge, no conflict, and no replay: the election recomputes, one store is authoritative, and writers resume writing current state. State is re-established by producers re-publishing what they know, not by reconciling divergent histories.

7. What it is not

Not replicated DSM. 1990s distributed shared memory replicated pages with no serialization point and manufactured an order from message traffic. FrogNet Memory has a controller; it does not solve that problem, it removes it.

Not a CRDT. There is no merge function and no lattice. The store does not converge; a value is current or it is not.

Not a cache. The SAME/DIFF machinery (spec §11) re-executes against the origin on every repeat and reports identity; it never answers from a stored copy.

Not transactional. No atomicity across two keys, no consensus, no exactly-once event delivery. Events that compound when duplicated do not belong in the store.

8. Limits

(a) One serialization point is a write-throughput ceiling; the coherence property and the scaling limit are the same fact. Writes are bounded by one point and do not scale horizontally; reads are relieved without a second copy. (b) No change notification; a consumer requiring timely observation MUST poll. (c) Composition across keys is the application's problem by design (§3).

9. References

Specification §9 (Memory), §8 (Election), §13 (Failure semantics). Build manual: Croakus §25, §29, §56. Tuple-space lineage: Gelernter and Carriero, Linda, Yale, 1985; the three-coordinate key is FrogNet's convention, not Linda's. Video walkthroughs: Intro to Tuples, Setting databasehost.frognet, Memory Backing Store.