Help us shape the future of Go! Take the 2026 Go Developer Survey

proposals

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package proposals is the service layer for the proposal decision ledger. It has a read-only ledger view, region and text filtering, evidence health, and previewed decisions with revalidation.

The CLI and the console share the evidence helper Health. They also share the decision flow (PreviewApply, PreviewDismiss, PreviewReconcile, Commit, with their Ledgerer, Preview, Result and ErrNothingSelected). Both surfaces thus judge proposal health by one set of rules. `lessons --apply` and `lessons --dismiss` write the same pins and ledger states as the console. An apply writes the pin file through internal/pinfile, the guarded editor of the console's Pins view.

MeasureOutcomes measures the applied pins. Health uses it after it reads the lessons config and the firing log. `lessons --stats` and the console's Stats view use MeasureOutcomes through internal/lessonstats, which reads the firing log once for all of its sections.

Only the console uses the ledger view (Load, MatchesRegion, MatchesRegionSet, MatchesText).

Index

Constants

View Source
const LessonsFile = ".seamark/lessons.yaml"

LessonsFile is the repository-relative path of the pin file.

Variables

View Source
var (
	// ErrNothingSelected reports an empty selection.
	ErrNothingSelected = errors.New("no pending proposal selected")
	// ErrStale reports that meaningful inputs changed after the
	// preview; the caller must build a new preview.
	ErrStale = errors.New("the ledger, the pin file, or the write gate changed since the preview")
	// ErrWriteDisabled reports an apply commit while distill.write is
	// off. The preview then offers the YAML block instead.
	ErrWriteDisabled = errors.New("distill.write is off")
	// ErrNeedsReconcile reports selected proposals whose pins already
	// exist in the pin file. A blind apply would duplicate them.
	ErrNeedsReconcile = errors.New("pins for the selected proposals already exist")
	// ErrNotPinned reports a reconcile selection with proposals whose
	// pins are not in the pin file. Reconcile records existing pins
	// only; a missing pin needs apply.
	ErrNotPinned = errors.New("no pin exists yet in " + LessonsFile + " for the selected proposals")
	// ErrFieldsDisagree reports an apply or reconcile selection with
	// proposals whose stored region fields disagree (pinfile.FieldsAgree).
	// Their apply writes a pin with another key than the key that every
	// reader links. So no reader links that pin to them, and no existing
	// pin is proven to be theirs. The preview refusal asks for a dismiss
	// or a fix by hand, because no decision of this package changes the
	// stored region fields.
	ErrFieldsDisagree = errors.New("the stored region fields of the selected proposals disagree")
	// ErrSharedKey reports an apply or reconcile selection with two or
	// more proposals that share one pin key. A *SharedKeyError carries
	// their IDs and matches this error.
	ErrSharedKey = errors.New("selected proposals share rule and regions")
	// ErrAppliedKey reports an apply or reconcile selection with a
	// proposal whose pin key an applied proposal already records. An
	// *AppliedKeyError carries the IDs and matches this error.
	ErrAppliedKey = errors.New("selected proposals have the rule and regions of an applied proposal")
)

Functions

func Health

func Health(st *store.Store, root string, ps []model.Proposal) (map[int64]report.ProposalHealth, error)

Health re-judges proposals under TODAY'S rules (RFC-002 §7): confidence tier over living evidence, the prompt era when the row predates the recurrence rules, and the regions current inference would assign. Purely derived — recomputed on every ask, stored nowhere, acted on only by explicit commands. It moved from internal/cli, so `seamark lessons --proposals` and the console show the same advisories. The error texts stay as the CLI printed them.

func MatchesRegion

func MatchesRegion(p model.Proposal, filter string) bool

MatchesRegion reports whether p applies to the repository-relative path scope filter. It matches on path-component boundaries in both directions. A repo-wide proposal matches every filter, and an empty filter matches every proposal.

func MatchesRegionSet

func MatchesRegionSet(set []string, filter string) bool

MatchesRegionSet reports whether a region set applies to the repository-relative path scope filter. It is MatchesRegion for a set that is not a proposal, such as the regions of a pin. It matches on path-component boundaries in both directions. An empty filter matches every set.

set must be an effective set: model.Proposal.RegionSet or reviews.PinRule.AllRegions. A nil or empty set is repo-wide and matches every filter. Only those readers apply the delivery rules for a "*" or a blank entry, so raw pin entries can give a wrong answer.

func MatchesText

func MatchesText(rule, note, q string) bool

MatchesText reports whether the text query q occurs in rule or in note. The match ignores case, and the query ignores surrounding spaces, because a typed search often carries a stray space. A blank query matches everything. The caller passes the text it shows, for example the live rule and note of a pin.

func MeasureOutcomes

func MeasureOutcomes(st *store.Store, cfg *reviews.Config, applied []model.Proposal,
	firings []reviews.Firing,
) (map[int64]outcome.Reading, error)

MeasureOutcomes measures the applied pins against a lessons config and firing records that the caller already read. Health uses it through appliedOutcomes. lessonstats.Gather reads the firing log once for all of its sections, so its delivery counts and its outcomes come from the same records. The error text stays as the CLI printed it.

Types

type Action

type Action int

Action is the kind of decision a preview describes.

const (
	// ActionApply turns pending proposals into pins.
	ActionApply Action = iota
	// ActionDismiss marks pending proposals dismissed. It writes only the
	// ledger, so a pin with the same rule and regions stays in the pin
	// file. A dismiss rejects a proposal, not an installed pin; a prune
	// of internal/maintain removes a pin.
	ActionDismiss
	// ActionReconcile records pending proposals as applied when their
	// pins already exist in the pin file. It writes only the ledger, so
	// a partial apply never needs a duplicate-producing retry.
	ActionReconcile
)

func (Action) String

func (a Action) String() string

String returns "apply", "dismiss" or "reconcile".

type AppliedKeyError

type AppliedKeyError struct {
	// Groups holds one group for each refused key. The groups are in the
	// order of their lowest pending ID.
	Groups []AppliedKeyGroup
}

AppliedKeyError reports the selected pending proposals whose pin key an applied proposal already records. Pins blocks a pin that two applied proposals record, so an apply must never record a second applied row for one key. Apply and reconcile refuse such a row, also when the pin file does not hold the pin: a new pin with that key is blocked in the same way.

func CheckAppliedKeys

func CheckAppliedKeys(applied, ps []model.Proposal) *AppliedKeyError

CheckAppliedKeys returns an *AppliedKeyError for the proposals of ps whose pin key a row of applied records, or nil. ps must be in ID order, with agreeing region fields. It links an applied row by reviews.ProposalKey, because Pins counts the applied rows of a key with that key. It reads nothing, so the console can apply the rule of the preview to its ledger snapshot.

func (*AppliedKeyError) Applied

func (e *AppliedKeyError) Applied() []int64

Applied returns the IDs of the applied proposals of every group, in ID order. A row has one key, so no ID is in two groups.

func (*AppliedKeyError) Error

func (e *AppliedKeyError) Error() string

Error names the applied proposals and the pending proposals to dismiss, for example "p1 is already applied with this rule and regions; dismiss p3". With several keys, each group names the pending proposals whose rule and regions it means, because "this" is then unclear.

func (*AppliedKeyError) Is

func (e *AppliedKeyError) Is(target error) bool

Is matches ErrAppliedKey, so a caller can test the kind with errors.Is and read the IDs with errors.As.

func (*AppliedKeyError) Pending

func (e *AppliedKeyError) Pending() []int64

Pending returns the IDs of the refused pending proposals of every group, in ID order. The caller dismisses them.

type AppliedKeyGroup

type AppliedKeyGroup struct {
	// Applied holds the IDs of the applied proposals with the key, in ID
	// order. An old ledger can hold more than one.
	Applied []int64
	// Pending holds the IDs of the selected pending proposals with the
	// key, in ID order.
	Pending []int64
}

AppliedKeyGroup names the proposals of one refused pin key.

type FileObservation

type FileObservation struct {
	Exists bool
	SHA256 [32]byte
}

FileObservation records the pin file state at preview time.

type Fingerprint

type Fingerprint struct {
	ID        int64
	Signature string
	Rule      string
	Note      string
	Regions   []string
}

Fingerprint is the identity of one proposal at preview time. IDs can be reused after a prune, so the ID alone is not enough.

type Ledger

type Ledger struct {
	// Pending, Applied and Dismissed hold the proposals by state, in
	// ascending ID order.
	Pending, Applied, Dismissed []model.Proposal
	// Superseded holds the proposals whose pins a prune removed, in
	// ascending ID order. They are read-only history: no decision can
	// change them, so Health does not cover them.
	Superseded []model.Proposal
	// Health holds evidence-health advisories by proposal ID.
	Health map[int64]report.ProposalHealth
	// HealthErr holds the advisory failure; Health is then empty.
	HealthErr error
	// Evidence holds the full cited findings by finding ID. A cited ID
	// that is missing aged out of the index.
	Evidence map[int64]model.Finding
	// EvidenceErr holds the evidence read failure.
	EvidenceErr error
	// Write is the current distill.write gate.
	Write bool
	// WriteErr holds the config read failure; Write is then false.
	WriteErr error
	// ObservedAt is the time of the snapshot.
	ObservedAt time.Time
}

Ledger is a read-only snapshot of the decision ledger. Each derived source keeps its own error, so one failed reader does not hide the proposals.

func Load

func Load(st *store.Store, root string, now time.Time) (*Ledger, error)

Load reads the ledger and its derived sources. It writes nothing. It fails only when the proposal rows cannot be read.

type Ledgerer

type Ledgerer interface {
	ProposalsByIDs(ids []int64) ([]model.Proposal, error)
	// Proposals returns the rows of one status. Apply and reconcile read
	// the applied rows, because Pins blocks a pin key that more than one
	// applied row records.
	Proposals(status string) ([]model.Proposal, error)
	SetProposalStatus(ids []int64, to string) (int, error)
}

Ledgerer is the ledger access a decision needs. *store.Store implements it; tests inject failures through it.

type Preview

type Preview struct {
	Action Action
	// Proposals holds the eligible selected proposals in ID order.
	Proposals []model.Proposal
	// Prints holds their fingerprints, parallel to Proposals.
	Prints []Fingerprint
	// Ineligible holds requested IDs that are no longer pending.
	Ineligible []int64
	// Write is the distill.write gate at preview time (apply only).
	Write bool
	// YAML is the pin block that apply inserts at the head of the
	// `pin:` list.
	YAML string
	// Target is the pin file path relative to the root.
	Target string
	// Lessons is the pin file observation (apply only).
	Lessons FileObservation
	// LessonsErr holds the pin file guard failure of an apply preview
	// taken with the gate off, for example a symbolic link. That
	// preview writes nothing, so the failure does not stop the YAML
	// block; Lessons is then zero and Commit refuses the preview.
	LessonsErr error
	// AlreadyPinned holds selected IDs whose pin already exists. No
	// applied proposal records such a pin, because the preview refuses
	// that case first (ErrAppliedKey).
	AlreadyPinned []int64
}

Preview describes one pending decision exactly. It is not an authorization: Commit rejects it when an input changed.

func PreviewApply

func PreviewApply(ls Ledgerer, root string, ids []int64) (*Preview, error)

PreviewApply validates the selection and describes the pin change. It writes nothing. It refuses three kinds of selection, with the gate on or off, because each gives a pin that Pins blocks at once. The first kind holds a proposal whose region fields disagree: no reader links the pin that its apply writes. The second holds a proposal whose pin key an applied proposal records (ErrAppliedKey). The third holds proposals that share one pin key (ErrSharedKey).

The applied key check runs before the shared key check. Its advice (dismiss) is final, and the apply of one row of a shared group meets it again. It also runs before the pin file read. So the pin of an applied proposal never reaches AlreadyPinned, which the CLI records as applied without a write.

func PreviewDismiss

func PreviewDismiss(ls Ledgerer, ids []int64) (*Preview, error)

PreviewDismiss validates the selection and describes the ledger change. It writes nothing.

func PreviewReconcile

func PreviewReconcile(ls Ledgerer, root string, ids []int64) (*Preview, error)

PreviewReconcile validates that every selected pending proposal already has its pin and describes the ledger-only transition to applied. It writes nothing. It refuses a proposal whose region fields disagree, as Pins and extraction do: the pin check reads the rendered key, and every other reader links the row by another key. It also refuses a proposal whose pin key an applied proposal records (ErrAppliedKey) and proposals that share one pin key (ErrSharedKey), as apply does: two applied proposals that record one pin make Pins block it. The applied key check runs before the pin check, so a pin of an applied proposal never reads as the pin of the selected one.

type Result

type Result struct {
	Action Action
	// FileWritten is true when the pin file changed.
	FileWritten bool
	// FileErr holds the pin file failure.
	FileErr error
	// Moved holds the IDs whose ledger state changed.
	Moved []int64
	// NotMoved holds the selected IDs whose ledger state did not change.
	NotMoved []int64
	// LedgerErr holds the ledger failure.
	LedgerErr error
}

Result reports what a commit changed. File and ledger outcomes stay separate, because the two writes are not one transaction.

func Commit

func Commit(ls Ledgerer, root string, pv *Preview) (Result, error)

Commit revalidates the preview and applies it. It never retries. A non-nil error means a revalidation refusal and no change; once a write starts, the Result fields report every outcome instead.

func (Result) Partial

func (r Result) Partial() bool

Partial reports a pin file write without a complete ledger update: the state that needs manual reconciliation.

type SharedKeyError

type SharedKeyError struct {
	// Groups holds the IDs of each shared key in ID order. The groups
	// are in the order of their lowest ID.
	Groups [][]int64
}

SharedKeyError reports the selected proposals that share one pin key. Apply and reconcile refuse them: two entries with one key are duplicate pins, and one entry that two applied proposals record is ambiguous. The link rules of internal/pinfile block both, so the console cannot change such a pin.

func (*SharedKeyError) Error

func (e *SharedKeyError) Error() string

Error names each group and the next step, for example "p1 and p2 share rule and regions; apply one and dismiss the other". A reconcile is an apply without a pin write, so the same advice fits both.

func (*SharedKeyError) Is

func (e *SharedKeyError) Is(target error) bool

Is matches ErrSharedKey, so a caller can test the kind with errors.Is and read the IDs with errors.As.

Jump to

Keyboard shortcuts

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