outbox

package
v0.4.2 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package outbox holds the ticket writes that have not reached the remote yet.

It exists because of a decision about how the tool should feel: claiming a ticket works offline. Being offline is rare enough that it must not dictate the interaction — so a claim is written locally straight away and the push is caught up when there is a network again. Something has to hold the write in between, and that is this.

It also settles a problem that would otherwise sit in the write path. Every ticket write funnels through core/ticket's Mutate and Create, and those are called from the TUI and from the merge driver as well as from the CLI. A synchronous push there would hang the board on the network and would have the merge driver pushing in the middle of a git merge. With an outbox, the write path only files the write; sending happens outside it.

The outbox lives under the per-working-tree state directory, not in the repository: an unsent write is this machine's business, and committing it would publish it in the very moment it is meant to be waiting.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Box

type Box struct{ Dir string }

Box is the queue for one working tree.

func At

func At(s *ticket.Store) *Box

At returns the box for a store's working tree.

func (*Box) Drop

func (b *Box) Drop(id string) error

Drop removes a queued write.

func (*Box) Flush

func (b *Box) Flush(s Sender) ([]Result, error)

Flush sends what it can and reports each entry.

It stops at the first unreachable remote instead of walking the rest of the queue. The network is a property of the machine, not of the ticket: once one push has proved there is no route, every following attempt would wait for the same timeout, and a command the user ran for another reason entirely would sit there for as long as the queue is.

A lost race is the opposite case and does not stop anything: it is about one ticket, and the next one may well go through.

func (*Box) List

func (b *Box) List() ([]Entry, error)

List returns every unsent write, oldest first. Ticket ids are ULIDs, so sorting by id is chronological by creation; QueuedAt orders by when the write was filed, which is what a flush should follow.

func (*Box) Pending

func (b *Box) Pending(id string) (Entry, bool)

Pending returns the unsent write for one ticket, if there is one. The board asks this to mark a card as carrying something not yet sent.

func (*Box) Queue

func (b *Box) Queue(id string, op Op, content []byte, lease, by string) error

Queue files a write, superseding any earlier unsent write for the same ticket. Superseding is correct rather than lossy: the entry carries the whole ticket, so the newer bytes already contain everything the older ones said.

func (*Box) QueueMilestone added in v0.3.0

func (b *Box) QueueMilestone(name string, op Op, content []byte, lease, by string) error

QueueMilestone files a pending write of a milestone file, under the same rules: whole content, the lease the remote last confirmed, superseding any earlier unsent write of the same milestone.

type Entry

type Entry struct {
	ID  string `json:"id"`
	Op  Op     `json:"op"`
	Ref string `json:"ref"`

	// Kind says which namespace ID belongs to. Absent on an entry written
	// before there were two, which means a ticket.
	Kind Kind `json:"kind,omitempty"`

	// Lease is the ref SHA the remote last confirmed to us, and it is the
	// compare-and-swap value. It survives being superseded: a second local
	// write while the first is still unsent replaces the content but keeps
	// this, because the question the remote answers is "has anyone written
	// since I last saw it", and our own unsent write is not an answer to that.
	Lease string `json:"lease"`

	// Content is the whole ticket file, not a patch. The file is the API, and
	// a queued write that replayed a diff could apply cleanly onto a state
	// nobody ever reviewed.
	Content string `json:"content,omitempty"`

	QueuedAt  time.Time `json:"queued-at"`
	UpdatedAt time.Time `json:"updated-at"`
	By        string    `json:"by,omitempty"`
}

Entry is one ticket's pending write.

func (Entry) Age

func (e Entry) Age() time.Duration

Age is how long this write has been waiting to be sent.

type Kind added in v0.3.0

type Kind string

Kind is which namespace a queued write belongs to: a ticket, keyed by its ulid, or a milestone, keyed by its name.

It is part of the path an entry is filed under, not only a field inside it. One flat directory keyed by the bare name would collide the moment somebody named a milestone after a ticket handle — and the collision would be silent, one unsent write overwriting the other.

const (
	// KindTicket is a ticket's own ref. It is the zero value's meaning too, so
	// an entry queued by an older build — which had no kinds and no
	// subdirectory — still reads back as the ticket write it is.
	KindTicket Kind = "tickets"
	// KindMilestone is a milestone file's ref.
	KindMilestone Kind = "milestones"
)

type Op

type Op string

Op is what the queued write does to the ticket's ref.

const (
	// OpWrite puts the ticket's current bytes on its ref.
	OpWrite Op = "write"
	// OpDelete removes the ref, for a ticket that has been logged or archived.
	OpDelete Op = "delete"
)

type Outcome

type Outcome string

Outcome is what became of one queued write during a flush.

const (
	// Sent means the remote accepted it and the entry is gone.
	Sent Outcome = "sent"
	// Rejected means someone else wrote the ticket first. The entry is
	// dropped: replaying it would only lose again, and the local file is still
	// there for the board to merge the ref into.
	Rejected Outcome = "rejected"
	// Unsent means the remote could not be reached. The entry stays.
	Unsent Outcome = "unsent"
	// Failed means git refused for some other reason. The entry stays, because
	// the cause is unknown and discarding a write on an unknown error is the
	// one outcome that loses work.
	Failed Outcome = "failed"
)

type Result

type Result struct {
	ID      string
	Kind    Kind
	Op      Op
	Outcome Outcome
	Err     error
}

Result reports one entry's fate.

type Sender

type Sender interface {
	Write(id string, content []byte, lease string) (string, error)
	Delete(id, lease string) error
	WriteMilestone(name string, content []byte, lease string) (string, error)
}

Sender is the transport a flush pushes through. It is an interface so the queue's behaviour — what it keeps, what it drops, when it stops — can be tested without a remote, and so nothing in here depends on a network being reachable.

Jump to

Keyboard shortcuts

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