journal

package
v0.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package journal is the append-only event log — one journal.ndjson per .spectackle folder, one JSON object per line. The journal is the historical source of truth: rejected and archived items leave work.md and live on here (searchable via the cache FTS as the rejection corpus).

Append-only has one sanctioned exception: compaction may fold old events, but `reject`, `archive` and `compact` lines are always retained verbatim.

Index

Constants

View Source
const (
	EvCreate  = "create"
	EvMove    = "move"
	EvRule    = "rule"
	EvArchive = "archive"
	EvReject  = "reject"
	EvDrift   = "drift"
	EvCompact = "compact"
	EvStart   = "start"  // worktree opened for an item
	EvSubmit  = "submit" // worktree gated, merged and replayed onto main
	EvAbort   = "abort"  // worktree abandoned

	// SDD orchestration v2 (see internal/lifecycle: Move reopen counter,
	// Escalate, ResolveBlocked).
	EvGrill    = "grill"    // grill feedback recorded against an item
	EvDecide   = "decide"   // a blocked item's linked decision was resolved
	EvEscalate = "escalate" // an item exhausted its feedback rounds and was blocked
	EvReview   = "review"   // independent review verdict, identity- and body-hash-bound (T-01KYD94KP4)
	EvValidate = "validate" // post-implementation validation: Op=render (pack, diff hash) or Op=verdict (T-01KYD94M3)
	EvBench    = "bench"    // benchmark put delta: Sum carries the verdicts, Body the superseded raw values (P-01KYJMVX2Q, ADR-01KYJMWEWQ)
	EvRevise   = "revise"   // draft-state body/targets revision through the draft tool (B-01KYER)
	EvGitSkip  = "gitskip"  // edge commit skipped after retries (index contention) — journal-only warning (T-01KYD94MG)
)

Event kinds (Ev field).

Variables

This section is empty.

Functions

func Append

func Append(root workspace.Root, ctx string, e Event) error

Append writes one event to the journal of a context dir, creating the scaffold if needed. It mints a unique event ID and stamps the workspace's agent name unless the caller provided them (replay does, verbatim).

func Rewrite

func Rewrite(root workspace.Root, ctx string, events []Event) error

Rewrite replaces a context dir's journal wholesale — used only by compaction (the sanctioned append-only exception).

Types

type Event

type Event struct {
	T    time.Time `json:"t"`
	Eid  string    `json:"eid,omitempty"` // unique event ID (minted on append) — the replay/idempotency backbone
	Ag   string    `json:"ag,omitempty"`  // agent that wrote the event
	Ev   string    `json:"ev"`
	ID   string    `json:"id,omitempty"`
	K    string    `json:"k,omitempty"`    // item kind
	Ti   string    `json:"ti,omitempty"`   // title
	Dir  string    `json:"dir,omitempty"`  // context dir
	Fr   string    `json:"fr,omitempty"`   // move: from state
	To   string    `json:"to,omitempty"`   // move: to state
	Note string    `json:"note,omitempty"` // move/reject/compact note
	Op   string    `json:"op,omitempty"`   // rule: add|edit|retire
	Rule string    `json:"rule,omitempty"` // rule/drift: rule ID
	Txt  string    `json:"txt,omitempty"`  // rule: full sentence (survives spec.md edits)
	Ap   []string  `json:"ap,omitempty"`   // rule: applies node IDs
	Sum  string    `json:"sum,omitempty"`  // archive/reject summary
	Body string    `json:"body,omitempty"` // reject: full item body (enables revocation)
	Tg   []string  `json:"tg,omitempty"`   // reject: item targets
	Refs []string  `json:"refs,omitempty"` // archive/reject: item refs — the research return path reads consumers from tombstones (T-01KYD88M)
	Par  string    `json:"par,omitempty"`  // reject: item parent
	// LEGACY: nothing writes this any more. item.Rules had no write path in
	// the entire tool surface and was deleted (B-01KYPC11VKF0Q); the field
	// stays so historical journals that carry a "rules" key still parse
	// rather than erroring, and omitempty means it is never emitted again.
	Rls  []string `json:"rules,omitempty"`
	Node string   `json:"node,omitempty"` // drift
	Cls  string   `json:"cls,omitempty"`  // drift: gone|changed|stale
	Oh   string   `json:"oh,omitempty"`   // drift: old hash
	Nh   string   `json:"nh,omitempty"`   // drift: new hash
	Item string   `json:"item,omitempty"` // drift: backprop item
	N    int      `json:"n,omitempty"`    // compact: events folded

	// SDD orchestration v2: reopen/grill/decide/escalate + reject snapshot.
	Rnd int      `json:"rnd,omitempty"` // move (reopen)/escalate/reject: Rounds counter
	Gr  string   `json:"gr,omitempty"`  // grill/reject: Grilled feedback
	Nd  []string `json:"nd,omitempty"`  // escalate/reject: Needs (blocking decision IDs)
	Ov  bool     `json:"ov,omitempty"`  // decide(override-once)/reject: Override spent

	// ADR template fields (reject/archive). item.Item carries these as
	// first-class fields but the Event had no channel for them at all, so a
	// rejected decision lost what it decided and a revoke could not restore
	// it. They were previously smuggled into Body as text, which meant they
	// shared the body's retention budget and a large one could amputate
	// another; addressable fields end that competition by construction.
	Ctx  string `json:"ctx,omitempty"`  // ADR Context
	Dec  string `json:"dec,omitempty"`  // ADR Decision
	Cons string `json:"cons,omitempty"` // ADR Consequences
	St   string `json:"st,omitempty"`   // ADR Status

	// Crt is the record's Created date, and is written ONLY for legacy
	// sequential IDs (P-0007), which carry no timestamp
	// (ADR-01KYNA70PQFTB). A modern record ID is a UUIDv7 whose mint time
	// IS the created date, so carrying it would duplicate a fact the ID
	// already asserts — and let the two disagree. Omitted, it costs nothing
	// on every event of the overwhelming majority.
	Crt string `json:"crt,omitempty"`

	// Gist is the one-line substance archive appended to spec.md's intent
	// section, carried so a worktree replay can reproduce that line BYTE
	// FOR BYTE on main instead of composing a second, shorter one. It
	// cannot be recomputed downstream: for a kind whose body the tombstone
	// does not retain, the gist derives from a body the event no longer
	// has. See lifecycle.IntentLine, the single composer both callers use.
	Gist string `json:"gist,omitempty"`

	// Review verdicts (EvReview) and the grill render they bind to
	// (T-01KYD94KP4): Hash is the sha256 of the item body at render/verdict
	// time — a body edit invalidates both by construction. Open is the
	// computed-finding count of the render; Pass the reviewer's verdict.
	Hash string   `json:"hash,omitempty"` // grill/review/validate: hash of the judged substance
	Open int      `json:"open,omitempty"` // grill/validate render: computed findings still open
	Pass bool     `json:"pass,omitempty"` // review/validate: the verdict
	Keys []string `json:"keys,omitempty"` // grill/validate render: the open finding keys (class:subject)
	Wv   []string `json:"wv,omitempty"`   // review/validate verdict: per-finding waivers, "key reason" (T-01KYD9J)
	Ln   []string `json:"ln,omitempty"`   // review verdict: lens labels the reviewer walked sequentially (T-01KYFXDC6)
}

Event is the single flat record type for all journal lines; unused fields are omitted. Keys are deliberately short — journals are read by machines and indexed, not read linearly by the LLM.

func Read

func Read(root workspace.Root, ctx string) ([]Event, error)

Read returns all events of one context dir (missing file = empty).

func ReadAll

func ReadAll(root workspace.Root) ([]Event, error)

ReadAll returns the events of every context dir, in context order.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL