Documentation
¶
Overview ¶
Bootstrap: derive a first ledger from what is on disk.
Purpose: a project that installed bundles before the ledger existed has no record of why its plugins are present. Bootstrap rebuilds that record from the installed plugin directories plus bundle membership (bundles.json). Inputs: Sources, built by the caller from internal/bundle and internal/plugin. Outputs: a valid Ledger. Constraints: a bundle counts as installed only when it is installable as a unit and EVERY member plugin is installed (a bundle install is all or nothing, with rollback). A plugin referenced by no installed bundle is explicit. Overlap is honoured: installed_by lists every installed bundle that contains the plugin. A plugin installed on its own AND later covered by a bundle cannot be told apart from the bundle install on disk; bootstrap treats it as bundle-owned, and P7-PLUG-19/20 record explicit installs from then on.
Package ledger is the bundle install-state ledger (contract:cli.bundle-ledger v1, P7 PLUG D6): per plugin directory, which bundles are installed and, per plugin, installed_by / explicit / tier / version / checksum.
Purpose: record WHY a plugin is present, so refcounted bundle remove (P7-PLUG-20), app-ready (P7-PLUG-19) and the surfaces (P7-SURF-13) stop inferring it from plugin directories. Inputs: a plugin directory (the file lives at <pluginDir>/.nself-state/bundles.json) and, for bootstrap, Sources (bundle membership plus installed plugins). Outputs: a validated Ledger, written atomically (temp + rename) under a file lock. The file is machine-owned: its first key is _generated and nothing in it is meant to be edited by hand. Constraints: this package imports neither internal/bundle nor internal/plugin, so bundle install/remove can import it without a cycle; callers adapt their own types to Sources. No new dependency.
Mutations: the three edits P7-PLUG-19 (app-ready) and P7-PLUG-20 (refcounted remove) make to a Ledger.
Purpose: keep the installed_by / explicit rules in one place (PLUG D6): a bundle remove drops a plugin only when installed_by becomes empty AND explicit is false. Inputs: a *Ledger (from Store.Update) and the bundle or plugin to record. Outputs: the Ledger is changed in place; RemoveBundle returns the plugins the caller may now remove. Constraints: pure, no I/O, and nothing here changes install or remove behaviour; callers decide what to do with RemoveBundle's answer.
Parse: read and strictly validate ledger bytes.
Purpose: turn a ledger file into a Ledger, refusing anything the schema or the contract refuses: a first key other than _generated, duplicate keys at any depth, unknown keys, missing or null required keys, trailing data, oversize input, and every Validate failure. A damaged ledger is reported, never replaced by a plausible default. Inputs: the file bytes. Outputs: a Ledger or an error. Constraints: pure; no file access (Store reads and caps the file).
Store: read and atomically update the ledger file of one plugin directory.
Purpose: the only code that touches <pluginDir>/.nself-state/bundles.json. Inputs: the plugin directory ($NSELF_PLUGIN_DIR or ~/.nself/plugins) and an optional Sources func used to bootstrap. The ledger lives with the plugins it describes (D-0284 option A), so it is the same for every project that shares that plugin directory and needs no project root. Outputs: Load (read only), Update (read, change, validate, write). Constraints: writers take an OS lock on bundles.json.lock (next to the ledger), write a temp file in the same directory, fsync it, then rename over the ledger, so a reader sees the old or the new file, never a partial one, and a crash between the temp write and the rename leaves the old ledger intact. The file is 0644 (it holds no secret), its directory 0700 (same as the other state directories). The directory name starts with a dot and holds no plugin.json, so plugin scans skip it. Load never writes and never creates a directory.
View: the per-bundle slice of the ledger that `nself bundle list --json` prints under the additive `ledger` key of each row.
Purpose: show a bundle's recorded state without exposing the whole file. Inputs: a Ledger, a bundle slug and that bundle's member plugin slugs. Outputs: a BundleView whose plugin list is sorted and never null. Constraints: pure; no file access.
Index ¶
- Constants
- Variables
- type BundleDef
- type BundleRecord
- type BundleView
- type InstalledPlugin
- type Ledger
- func (l *Ledger) AddBundle(slug string, members []InstalledPlugin, at time.Time)
- func (l Ledger) Clone() Ledger
- func (l *Ledger) MarkExplicit(p InstalledPlugin)
- func (l Ledger) Marshal() ([]byte, error)
- func (l *Ledger) RemoveBundle(slug string) ([]string, error)
- func (l Ledger) Validate() error
- func (l Ledger) View(slug string, members []string) BundleView
- type PluginRecord
- type PluginView
- type Sources
- type Store
Constants ¶
const ( // Generated is the value of the first key, _generated. Generated = "GENERATED BY nself — DO NOT HAND EDIT" // SchemaVersion is the only schema_version this package reads or writes. SchemaVersion = 1 // TierFree and TierLicensed are the two tiers a plugin record may carry. TierFree = "free" TierLicensed = "licensed" // MaxBytes caps the size of a ledger file that Parse and Load accept. MaxBytes = 4 << 20 // SlugPattern is the shape of a bundle or plugin slug used as a map key. SlugPattern = `^[a-z0-9][a-z0-9._-]*$` // ChecksumPattern is a lowercase hex sha256, or empty when unknown. ChecksumPattern = `^([0-9a-f]{64})?$` )
const StateDirName = ".nself-state"
StateDirName is the directory inside the plugin directory that holds the ledger.
Variables ¶
var Tiers = []string{TierFree, TierLicensed}
Tiers lists the accepted tier values.
Functions ¶
This section is empty.
Types ¶
type BundleDef ¶
type BundleDef struct {
Slug string
// Installable is false for structural bundles (task, nself-plus) that are
// never installed as a unit; they are never recorded as installed.
Installable bool
Plugins []string
}
BundleDef is one bundle's membership.
type BundleRecord ¶
BundleRecord is one installed bundle.
type BundleView ¶
type BundleView struct {
Installed bool `json:"installed"`
InstalledAt string `json:"installed_at,omitempty"`
Plugins []PluginView `json:"plugins"`
}
BundleView is one bundle's ledger state.
type InstalledPlugin ¶
type InstalledPlugin struct {
Name string
Version string
Tier string // free | licensed; anything else is treated as free
Checksum string // lowercase hex sha256 or empty
}
InstalledPlugin is one plugin found on disk.
type Ledger ¶
type Ledger struct {
Generated string `json:"_generated"`
SchemaVersion int `json:"schema_version"`
Bundles map[string]BundleRecord `json:"bundles"`
Plugins map[string]PluginRecord `json:"plugins"`
}
Ledger is the file. Field order is the on-disk key order: _generated first.
func BootstrapReport ¶
BootstrapReport builds the first ledger from s. A plugin or bundle whose name is not a valid slug cannot be written to the ledger (one such name would make every later write fail), so it is skipped and returned as a description the caller can show. A bundle with a skipped member is not installed.
func Parse ¶
Parse reads and validates ledger bytes. Unknown keys, a first key other than _generated, trailing data and every Validate failure are errors: a damaged ledger is reported, never replaced by a plausible default.
func (*Ledger) AddBundle ¶
func (l *Ledger) AddBundle(slug string, members []InstalledPlugin, at time.Time)
AddBundle records bundle slug as installed at `at` with the given member plugins. Existing plugin records keep their explicit flag; the bundle is added to their installed_by. A new plugin record is explicit=false. Members may be passed by name only: a fact the input does not carry (version, checksum, a tier other than free or licensed) keeps its recorded value. Re-adding a bundle that is already recorded keeps its first installed_at.
func (*Ledger) MarkExplicit ¶
func (l *Ledger) MarkExplicit(p InstalledPlugin)
MarkExplicit records that the owner installed the plugin on its own, so a bundle remove keeps it. A plugin not yet recorded is added with an empty installed_by.
func (Ledger) Marshal ¶
Marshal validates and renders the canonical bytes: 2-space indent, sorted map keys, _generated first, trailing newline. It does not change l.
func (*Ledger) RemoveBundle ¶
RemoveBundle forgets bundle slug and returns, sorted, the plugins that no installed bundle references any more and that were not installed explicitly: those are the ones a bundle remove may delete. A plugin still referenced by another bundle, or explicit, stays in the ledger and is not returned. An unknown bundle is an error and changes nothing.
type PluginRecord ¶
type PluginRecord struct {
// InstalledBy lists the installed bundles that reference the plugin, sorted.
InstalledBy []string `json:"installed_by"`
// Explicit is true when the owner installed the plugin on its own, so a
// bundle remove must keep it.
Explicit bool `json:"explicit"`
Tier string `json:"tier"`
Version string `json:"version"`
// Checksum is the lowercase hex sha256 of the installed artifact, or empty
// when the install did not record one.
Checksum string `json:"checksum"`
}
PluginRecord says why one plugin is present.
type PluginView ¶
type PluginView struct {
Slug string `json:"slug"`
InstalledBy []string `json:"installed_by"`
Explicit bool `json:"explicit"`
Tier string `json:"tier"`
Version string `json:"version"`
Checksum string `json:"checksum"`
}
PluginView is one member plugin as the ledger records it.
type Sources ¶
type Sources struct {
Bundles []BundleDef
Plugins []InstalledPlugin
// Now stamps installed_at; nil means time.Now.
Now func() time.Time
}
Sources is everything Bootstrap reads. A Sources value must come from a successful read of the plugin directory and bundle membership: an unreadable source is an error for the caller (see Store.Sources), never an empty Sources.
type Store ¶
type Store struct {
// Sources supplies bootstrap input when the file does not exist yet. It
// returns an error when the installed plugins or bundle membership cannot be
// read. A missing file with a nil Sources, or a Sources error, is an error
// (never an empty ledger): an empty ledger would be written and then hide
// the real install state, because bootstrap runs only once.
Sources func() (Sources, error)
// Warn, when set, receives what Bootstrap had to skip (invalid slugs).
Warn func(msg string)
// contains filtered or unexported fields
}
Store is the ledger of one plugin directory.
func (*Store) EnsureBootstrapped ¶
EnsureBootstrapped writes the bootstrap ledger when no file exists and reports whether it did. An existing ledger is left untouched.