Raxol.Agent.Journal.Tip (Raxol Journal v2.8.0)

Copy Markdown View Source

The FROZEN conversational-tip predicate — the single contract implementation of conversational?/1 and tip/2.

Frozen contract. A resume locates its point by a backward scan under this predicate. Raxol.Agent.Journal.Records.Checkpoint.FileBackend validates a stamped tip_offset against the same predicate, with its own copy of the whitelist.

The predicate

conversational?(record) :=
  record.kind == "event"
  ∧ record.family == "loop"
  ∧ record.type ∈ CONVERSATIONAL

tip(journal, branch := "main") :=
  the record with the HIGHEST offset satisfying
  (record.branch_id == branch ∧ conversational?(record))
  (undefined on an empty/no-conversational branch → :no_tip)

Frozen decisions this module encodes

  • The grandfather clause. A record with no "kind" reads as "event"; a record with no "branch_id" reads as "main". Every journal written before those keys existed therefore keeps its historical tip byte-for-byte.
  • The closure rule. CONVERSATIONAL is a whitelist and the ONLY door: every future record kind and every future family: "loop" type is tip-excluded unless explicitly added here. Nothing becomes a tip by default.
  • The exclusions. state_change, idle, every family: "meta" event, every non-"event" kind, and — deliberately, though it is family: "loop" — woken are excluded. A trailing checkpoint / meta event / idle marker / woken must never be selected as the tip.
  • approval_requested IS included — a pending approval is exactly where a resumed conversation must land (emitted family: "loop", not :meta).

This module is pure and reader-side only: it never touches the journal, the bus, or a writer. It reads decoded journal records (string-keyed maps) as produced by the tolerant Reader or by an independent raw decoder.

Summary

Functions

Is record a conversational loop event — a legal tip candidate?

The frozen CONVERSATIONAL whitelist, as a list of type strings.

The conversational tip of records on branch (default "main").

Functions

conversational?(record)

@spec conversational?(map()) :: boolean()

Is record a conversational loop event — a legal tip candidate?

true iff kind == "event" (grandfathered from absent), family == "loop", and type ∈ CONVERSATIONAL. Everything else — checkpoints, family: "meta" events, idle/woken/state_change loop events, unknown future kinds — is false, by the closure rule.

conversational_types()

@spec conversational_types() :: [String.t()]

The frozen CONVERSATIONAL whitelist, as a list of type strings.

tip(records, branch \\ "main")

@spec tip([map()], String.t()) :: {:tip, non_neg_integer()} | :no_tip

The conversational tip of records on branch (default "main").

Returns {:tip, offset} for the highest-offset record satisfying branch_id == branch ∧ conversational?, or :no_tip when the branch has no conversational record. records may arrive in any order — the tip is defined by highest offset, not list position (a backward scan and a max_by agree).

tip(records) is shorthand for tip(records, "main"); every pre-branch_id record reads as branch "main", so the default is grandfather-safe.