workspace

package
v0.1.13 Latest Latest
Warning

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

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

Documentation

Overview

Package workspace is the working-copy half of configuration versioning (the history half is core/revision): each administrator has one workspace, edited by them (in any console) and by their agents, and committed as a revision. A workspace holds items (what a codec makes of a configuration: an entity, a role, the settings...) that may be invalid while edited, like code half written. Values merge three ways (see Merge): changes made on both sides to the same value are conflicts, which keep the local value and block committing until resolved; nothing is overwritten silently.

The package knows items, not what they mean: the rules a merge needs (how list elements are keyed, which text is optional) come in a Schema.

Example (PathString)
fmt.Println(Path{{Field: "grants"}, {Element: "orders"}, {Field: "actions"}})
Output:
grants[orders].actions

Index

Examples

Constants

View Source
const (
	SourcePublished = "published"
	SourceDraft     = "draft"
	SourceWorkspace = "workspace"
)

The kinds of sources.

Variables

View Source
var ErrChanged = errors.New("workspace: changed since it was read")

ErrChanged: the workspace was written since it was read.

View Source
var Missing any = missing{}

Missing is a value that is not there.

Functions

func IsMissing

func IsMissing(v any) bool

IsMissing reports whether v is Missing.

func NewID

func NewID() string

NewID is a random conflict ID.

Types

type Change

type Change struct {
	Path  Path
	Value any
}

Change is a value that differs: its path and its value after (Missing: removed).

type Conflict

type Conflict struct {
	ID     string `json:"id"`
	Ref    Ref    `json:"ref"`
	Path   Path   `json:"path"`
	Source Source `json:"source"`
	Pinned bool   `json:"pinned,omitempty"`
	// Theirs (pinned only), TheirsGone when that side removed it.
	Theirs     any  `json:"theirs,omitempty"`
	TheirsGone bool `json:"theirsGone,omitempty"`
}

Conflict is a value of an item two sides changed differently. The workspace keeps its own value; the other side's is the base's (a published revision's) or, when pinned, the one held here.

type Item

type Item struct {
	Ref
	Value any `json:"value"`
}

Item is one item with its value (decoded JSON).

type LogEntry

type LogEntry struct {
	Version int64     `json:"version"`
	At      time.Time `json:"at"`
	By      string    `json:"by"`
	// Op is what wrote it (edit, apply, update, merge, resolve, reset,
	// commit, checkpoint); Items what changed.
	Op    string `json:"op"`
	Items []Ref  `json:"items"`
	Note  string `json:"note,omitempty"`
}

LogEntry records a write of a workspace.

type MemoryStore

type MemoryStore struct {
	// contains filtered or unexported fields
}

MemoryStore keeps workspaces in memory, for tests and single-process use.

func NewMemoryStore

func NewMemoryStore(now func() time.Time) *MemoryStore

NewMemoryStore returns an empty MemoryStore. A nil now uses time.Now.

func (*MemoryStore) PutWorkspace

func (m *MemoryStore) PutWorkspace(_ context.Context, w Workspace, expected int64, entry LogEntry) (Workspace, error)

PutWorkspace implements Store.

func (*MemoryStore) Workspace

func (m *MemoryStore) Workspace(_ context.Context, username string) (Workspace, bool, error)

Workspace implements Store.

func (*MemoryStore) WorkspaceLog

func (m *MemoryStore) WorkspaceLog(_ context.Context, username string, limit int) ([]LogEntry, error)

WorkspaceLog implements Store.

type Note

type Note struct {
	Source string `json:"source"`
	By     string `json:"by"`
	Text   string `json:"text"`
}

Note says where changes came from: a plan an agent applied or a draft taken in, with what its author said.

type Open

type Open struct {
	Conflict
	Mine, TheirsValue any
}

Open is a conflict as it stands: the workspace's value and the other side's.

type Path

type Path []Step

Path is a sequence of steps; empty is the whole value.

func (Path) Pattern

func (p Path) Pattern() string

Pattern is the path without its element keys, e.g. fields[].mask: the form Schema rules are written in.

func (Path) String

func (p Path) String() string

String is a readable path, e.g. fields[email].mask.

type Ref

type Ref struct {
	Kind string `json:"kind"`
	Key  string `json:"key"`
}

Ref names an item: its kind and its key within the kind.

type Schema

type Schema struct {
	// Lists are the lists whose elements are keyed, by the pattern of their
	// elements ("fields[]"), with the property keying them ("name"); any
	// other list is one value.
	Lists map[string]string
	// Unset are the values whose zero (empty text, false, null) means unset,
	// equal to no value at all.
	Unset map[string]bool
}

Schema holds the rules the merge needs of the values it merges, by path from an item's root as Path.Pattern writes it ("fields[].mask": the mask of any element of fields). A rule applies at its path only, never to a property of the same name elsewhere.

func (Schema) Canonical

func (s Schema) Canonical(v any) string

Canonical is the canonical JSON of an item: keys sorted, unset values dropped.

func (Schema) Carry

func (s Schema) Carry(pending, found []Conflict, now, base Snapshot, source *Source) []Conflict

Carry keeps the conflicts not resolved before (pending) next to those just found: a new one replaces those on or under its value (a removed item replaces the conflicts on its values); one on or under a pending conflict still standing is left to it. A pending conflict against the base (unpinned) stands, after an update, against the newest base (source).

func (Schema) Changes

func (sc Schema) Changes(a, b Snapshot) []Ref

Changes are the refs whose value differs between a and b.

func (Schema) Diff

func (s Schema) Diff(a, b any) []Change

Diff lists the values that differ from a to b, at the depth the merge compares them (objects by key, keyed lists by element, a group of elements sharing a key as a whole), in a stable order.

func (Schema) GetAt

func (s Schema) GetAt(root any, path Path) any

GetAt is the value at path, or Missing; a group for elements sharing a key.

func (Schema) Merge

func (s Schema) Merge(base, local, next any, path Path) (any, []ValueConflict)

Merge merges base → local and base → next, the values at path.

func (Schema) OpenConflicts

func (s Schema) OpenConflicts(now, base Snapshot, cs []Conflict) []Open

OpenConflicts are the conflicts that stand in now.

func (Schema) Replay

func (s Schema) Replay(old, local, next Snapshot, source Source, pinned bool) (Snapshot, []Conflict)

Replay re-applies the changes from old to local onto next: an item only local changed takes local's value, others next's; a value both changed differently keeps local's and is returned as a conflict against source (pinned: next's value held in the conflict).

func (Schema) Resolve

func (s Schema) Resolve(now, base Snapshot, cs []Conflict, id string, theirs bool) (Snapshot, []Conflict, bool)

Resolve resolves conflict id: mine keeps the workspace's value, theirs takes the other side's (removing it when removed there). Its choice decides the values under it on its side too; what now matches the other side needs no choice. It returns the workspace and the conflicts left, or false when id is not an open conflict.

func (Schema) Same

func (s Schema) Same(a, b any) bool

Same reports whether two items mean the same, regardless of key order or unset values.

func (Schema) SetAt

func (s Schema) SetAt(root any, path Path, value any) any

SetAt returns root with the value at path replaced, or removed when value is Missing; missing objects on the way are created, and a missing list element is appended.

func (Schema) View

func (s Schema) View(now, base Snapshot, c Conflict) (Open, bool)

View is c as it stands in the workspace now, against base (its base revision's items): its path (a path through a list element that has since become a group stops at the group), the workspace's value and the other side's; false when there is nothing left to choose, because the workspace holds the other side's value already (for instance typed in by hand) or removed what holds it (the item, a list element or object on the way).

type Snapshot

type Snapshot []Item

Snapshot is a configuration as items, in order. Lookups by ref walk it; an operation that looks many up indexes it first (Index), and one that changes many items applies them at once (Apply): a configuration may hold thousands of items.

func (Snapshot) Apply

func (s Snapshot) Apply(values map[Ref]any, order []Ref) Snapshot

Apply returns s with each ref of order set to its value in values: replaced where it is, appended when absent (in that order), or removed when the value is Missing. It walks s once.

func (Snapshot) Get

func (s Snapshot) Get(ref Ref) any

Get is the value of ref, or Missing.

func (Snapshot) Index

func (s Snapshot) Index() map[Ref]int

Index is the position of each item by ref.

func (Snapshot) With

func (s Snapshot) With(ref Ref, value any) Snapshot

With returns s with ref's value replaced where it is, appended when absent, or removed when value is Missing.

type Source

type Source struct {
	Kind string `json:"kind"`
	// ID is the revision or draft; By who wrote, for a workspace write.
	ID string `json:"id,omitempty"`
	By string `json:"by,omitempty"`
}

Source is where the other side of a conflict comes from: a revision published since the workspace's base (published), a draft taken in (draft), or a write of the same workspace made meanwhile (workspace: by another console or an agent).

type Step

type Step struct {
	Field   string `json:"field,omitempty"`
	Element string `json:"element,omitempty"`
}

Step is a step into a value: an object property, or the element of a list with a key.

type Store

type Store interface {
	// Workspace is username's workspace; false when none is kept.
	Workspace(ctx context.Context, username string) (Workspace, bool, error)
	// PutWorkspace writes w while the kept one is at version expected (0:
	// none), recording entry with the new version, in one transaction; it
	// returns w as written, or ErrChanged.
	PutWorkspace(ctx context.Context, w Workspace, expected int64, entry LogEntry) (Workspace, error)
	// WorkspaceLog is username's log, newest first; limit <= 0 is all.
	WorkspaceLog(ctx context.Context, username string, limit int) ([]LogEntry, error)
}

Store keeps workspaces and their write logs.

type ValueConflict

type ValueConflict struct {
	Path Path
	// Mine is the local value, kept; Missing when removed locally.
	Mine any
	// Theirs is the other side's value; Missing when removed there.
	Theirs any
}

ValueConflict is a value both sides changed differently.

type Workspace

type Workspace struct {
	Username string
	// Base is the revision it builds on; Items its content, nil the base's.
	Base      int64
	Items     Snapshot
	Conflicts []Conflict
	Notes     []Note
	// Taken are drafts taken in; Checkpoint the draft last saved from it,
	// at version CheckpointAt (the workspace holds it while still there).
	Taken        []int64
	Checkpoint   int64
	CheckpointAt int64
	Version      int64
	UpdatedAt    time.Time
	// UpdatedBy is who wrote last: the administrator or agent:<token name>.
	UpdatedBy string
}

Workspace is an administrator's workspace as kept.

Directories

Path Synopsis
Package workspacetest is the conformance suite every workspace.Store implementation runs: in memory and on each configuration store database.
Package workspacetest is the conformance suite every workspace.Store implementation runs: in memory and on each configuration store database.

Jump to

Keyboard shortcuts

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