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
- Variables
- func Health(st *store.Store, root string, ps []model.Proposal) (map[int64]report.ProposalHealth, error)
- func MatchesRegion(p model.Proposal, filter string) bool
- func MatchesRegionSet(set []string, filter string) bool
- func MatchesText(rule, note, q string) bool
- func MeasureOutcomes(st *store.Store, cfg *reviews.Config, applied []model.Proposal, ...) (map[int64]outcome.Reading, error)
- type Action
- type AppliedKeyError
- type AppliedKeyGroup
- type FileObservation
- type Fingerprint
- type Ledger
- type Ledgerer
- type Preview
- type Result
- type SharedKeyError
Constants ¶
const LessonsFile = ".seamark/lessons.yaml"
LessonsFile is the repository-relative path of the pin file.
Variables ¶
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") // 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 ¶
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 ¶
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 ¶
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 )
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 ¶
FileObservation records the pin file state at preview time.
type Fingerprint ¶
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.
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 ¶
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 ¶
PreviewDismiss validates the selection and describes the ledger change. It writes nothing.
func PreviewReconcile ¶
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.
type SharedKeyError ¶
type SharedKeyError struct {
// 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.