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

wasmplugin

package
v0.48.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 62 Imported by: 0

Documentation

Overview

Package wasmplugin runs filex "app plugins": WebAssembly modules executed inside the filex process by wazero (through the Extism host SDK), with no access to the host's filesystem, network, environment or database — only to the host functions filex exposes, each gated by a permission the administrator granted at install.

Why in-process wasm and not a subprocess or a sidecar

The storage-plugin subsystem (internal/plugin) starts a plugin BINARY in filex's own container: same filesystem, same environment, FILEX_SECRET_KEY in reach. That is fine for a driver the operator built, and unacceptable for third-party code a user installs from a URL. A container per plugin needs a Docker socket somewhere; an OS sandbox is strong on Linux and absent on the other two platforms filex ships for. A wasm sandbox is the same on all three, costs no extra process, and cannot reach anything it is not handed.

What the runtime guarantees

  • wazero's compiler backend only (amd64/arm64). The interpreter is refused, not tolerated: measured elsewhere, it takes every core for minutes.
  • A fresh instance per call: no state survives between users or jobs, memory is returned when the call ends.
  • A memory ceiling (Manifest.MemoryPages) and a wall-clock budget per call; the instance is torn down when either is hit.
  • No WASI filesystem preopens, no environment, no arguments.
  • Compiled modules are cached on disk (WithCompilationCache), so a 20–50 MB Go module is compiled once per host, not once per boot.

Index

Constants

View Source
const (
	CodePluginError = "plugin_error" // the guest set an error message
	CodePluginTrap  = "plugin_trap"  // the guest crashed (unreachable, OOB…)
	CodePluginOOM   = "plugin_oom"   // the guest hit its memory ceiling
	CodeTimeout     = "timeout"      // the call outlived its budget
	CodeRefused     = "refused"      // describe disagrees with the manifest
	CodeUnsupported = "unsupported"  // this host cannot run plugins (arch)
	CodeBusy        = "busy"         // every screen-call slot of the plugin is taken
)

Outcome codes a guest call can end with. They are what the job row, the admin log and the user-facing message key on — never a wazero stack.

View Source
const (
	ParamPageTokenHash = "page_token_hash"
	ParamShareID       = "share_id"
)

Parameter names only the HOST writes into a job row: the per-job output a person chose on a screen (outputOverrideKey, which runJob obeys without asking again), and the stamps the public page door adds (which visitor's link acted — an app binds a signature to a signer by it).

View Source
const (
	KindApp          = "app"
	KindLanguagePack = "language_pack"
)

The two kinds of app, as Status.Kind and DryRunAnswer.Kind say them.

View Source
const (
	DefaultMemoryPages = 1024 // 64 MiB
	MaxMemoryPages     = 4096 // 256 MiB
	DefaultCallTimeout = 15   // seconds, views/pages/describe
	MaxCallTimeout     = 60
	DefaultJobTimeout  = 300 // seconds, action_run
	MaxJobTimeout      = 900
	// MaxTickTimeout caps the hourly wake-up. It is deliberately tighter
	// than a screen's ceiling: a screen has a person waiting and 60 s is
	// defensible, while a wake-up runs unattended and one that hangs holds
	// up every other app's schedule behind it.
	MaxTickTimeout = 30
)

Limits the host imposes on what a manifest may ask for.

View Source
const (
	StateRunning  = "running"
	StateDisabled = "disabled"
	StateRefused  = "refused"
	StateFailed   = "failed"
)

Plugin states.

View Source
const (
	ErrCodeManifestInvalid       = "manifest_invalid"
	ErrCodeSHA256Mismatch        = "sha256_mismatch"
	ErrCodeSHA256Required        = "sha256_required"
	ErrCodeSignatureRequired     = "signature_required"
	ErrCodeSignatureInvalid      = "signature_invalid"
	ErrCodePermissionsIncomplete = "permissions_incomplete"
	ErrCodePermissionsChanged    = "permissions_changed"
	ErrCodeNameTaken             = "name_taken"
	ErrCodeDescribeMismatch      = "describe_mismatch"
	ErrCodeTooLarge              = "too_large"
	ErrCodeDemo                  = "demo_refused"
	ErrCodeFetch                 = "fetch_failed"
	ErrCodeNotFound              = "not_found"
	// ErrCodeIncompatible: the manifest's `filex` range leaves the running
	// filex out (compat.go).
	ErrCodeIncompatible = "incompatible"
	// ErrCodeUpToDate: "upgrade from its source" found nothing newer there
	// (updates.go FetchUpdate).
	ErrCodeUpToDate = "up_to_date"
)

Install error codes.

View Source
const (
	// FetchReasonBadRepo: the repository is not written as owner/name.
	FetchReasonBadRepo = "bad_repo"
	// FetchReasonManifestNotFound: no filex-app.json at any ref tried — a
	// repository that does not exist, is private, has no manifest at its
	// root, or a ref that does not exist.
	FetchReasonManifestNotFound = "manifest_not_found"
	// FetchReasonModuleNotFound: the manifest names a module address that
	// answers 404 — usually a release whose asset was never uploaded.
	FetchReasonModuleNotFound = "module_not_found"
	// FetchReasonUnreachable: no answer at all (DNS, network, TLS).
	FetchReasonUnreachable = "unreachable"
	// FetchReasonHTTPStatus: an answer, but not a success or a 404.
	FetchReasonHTTPStatus = "http_status"
	// FetchReasonBadURL: not an https address (plain http only for loopback).
	FetchReasonBadURL = "bad_url"
	// FetchReasonMissingURL: a URL install without its addresses — the
	// manifest always, the module unless the manifest is a language pack.
	FetchReasonMissingURL = "missing_url"
	// FetchReasonTooLarge: the answer was larger than the cap.
	FetchReasonTooLarge = "too_large"
	// FetchReasonChanged: the source answered a different version between
	// the update check's read and the install's (updates.go).
	FetchReasonChanged = "changed"
)

Why a fetch failed (InstallError.Reason on fetch_failed).

View Source
const (
	// TickInterval is how often a scheduled app is woken. The wake-up is
	// re-armed to the next boundary of this interval, so an instance that
	// has been up for a week still wakes on the hour.
	TickInterval = time.Hour
	// MaxScheduleItems / MaxSchedulePaths are the wire contract's numbers,
	// not a second copy: the guest is told them in every TickInput and the
	// test kit checks an answer against them, so they are named once in
	// pkg/pluginkit/wire and enforced here.
	MaxScheduleItems = wire.ScheduleMaxItems
	MaxSchedulePaths = wire.ScheduleMaxPaths
)

Bounds. Everything a wake-up can ask for is capped here; a plugin is told the two it has to plan around (MaxScheduleItems, MaxSchedulePaths) in its TickInput, so it can choose what to drop rather than having it chosen.

View Source
const (
	// UpdateCurrent: nothing newer that this filex can run.
	UpdateCurrent = "current"
	// UpdateAvailable: a newer version waits for the administrator.
	UpdateAvailable = "available"
	// UpdateNeedsApproval: a newer version asks for more than was granted.
	UpdateNeedsApproval = "needs_approval"
	// UpdateIncompatible: newer versions exist, but none this filex can run.
	UpdateIncompatible = "incompatible"
	// UpdateFailed: an automatic update was tried and undone (filex 0.47,
	// which applied updates by itself; a row may still carry it).
	UpdateFailed = "failed"
	// UpdateCheckFailed: the source could not be read (nothing was tried).
	UpdateCheckFailed = "check_failed"
)

What the last check found (UpdateInfo.Status).

View Source
const (
	// DefaultMaxUIBytes is the zipped bundle's ceiling unless
	// FILEX_APP_PLUGIN_MAX_UI_MB says otherwise. draw.io's whole editor is about
	// 60 MB zipped; 128 MiB leaves room without letting an app park arbitrary
	// data on the server.
	DefaultMaxUIBytes = 128 << 20
)
View Source
const UIPrefix = "/_appui/"

UIPrefix is the route's first segment.

Variables

View Source
var (
	ErrPageNotFound = errors.New("page not found")
	ErrPageGone     = errors.New("page is no longer available")
)

Page errors the HTTP layer maps to status codes. The PIN-gate errors are internal/share's (ErrBadPIN / ErrLocked): there is one gate now.

View Source
var ErrUnsupportedArch = errors.New("wasmplugin: app plugins need an amd64 or arm64 host (wazero's interpreter is refused — it would take every core)")

ErrUnsupportedArch is returned by New on a CPU wazero has no compiler for.

View Source
var HostVersion = "dev"

HostVersion is what `describe` is told; filled by the server from its build.

View Source
var SurfaceNodeTypes = map[string]bool{
	"text": true, "form": true, "steps": true, "list": true, "progress": true,
	"people-picker": true, "pin-input": true, "file-chooser": true, "pdf-fields": true,
	"signature-pad": true, "preview": true, "divider": true, "row": true,
}

SurfaceNodeTypes is the v1 catalogue.

Functions

func ArchSupported

func ArchSupported() bool

ArchSupported reports whether wazero has a compiler for this CPU.

func CheckOpen

func CheckOpen(p *Installed, s *wire.Surface) error

CheckOpen validates a surface's `open` against the plugin that answered: the screen it names must be that plugin's own, and it may not name both. The PATH is checked by the handler, which is where a caller's permissions are known — a screen must not become a way to look at a file the person asking could not open themselves.

func CompatEnforced added in v0.47.0

func CompatEnforced() bool

CompatEnforced reports whether this filex checks app ranges at all (false on a development build).

func DecodeInstallBody

func DecodeInstallBody(b []byte) (map[string]json.RawMessage, error)

decodeJSONBody is a small helper for the handlers' JSON install bodies.

func EncodeJobText added in v0.46.0

func EncodeJobText(t wire.Text) string

EncodeJobText is t as a job column: one plain string when every language says the same (or only one was written), else every language, marked.

func ExposedFiles

func ExposedFiles(sh *model.Share) []wire.FileRef

ExposedFiles lists the copies an app link exposed, straight off the row.

⚠ Deliberately a free function and not a Registry method: the no-JS page must be able to hand a visitor the document they were sent even on an instance where the plugin runtime is unavailable (an architecture without wasm, a plugin that failed to load). The copies are already on disk and belong to the share, not to the running app.

func FilexVersion added in v0.47.0

func FilexVersion() string

FilexVersion is the running filex as the Apps screen names it: x.y.z of a release, or the build's own version string on a development build.

func IsCode

func IsCode(err error, code string) bool

IsCode reports whether err is a CallError with the given code.

func IsNewDocumentKey added in v0.48.0

func IsNewDocumentKey(key string) bool

IsNewDocumentKey reports whether a create request names an app's row.

func JobText added in v0.46.0

func JobText(stored, lang string) string

JobText reads a job column in lang (wire.Text.Get: the language, its base language, English, any). A plain column — every row written before EncodeJobText, a progress line, the host's own words — comes back as it is.

func JobTimeout

func JobTimeout(l wire.Limits) int

JobTimeout is the clamped budget for one action_run of an action.

func JudgeFilexRange added in v0.48.0

func JudgeFilexRange(rng string) (ok bool, requires, filex string, err error)

JudgeFilexRange reads a bare `filex` range the way an app's is read and judges it against the running filex — for a storage plugin's feed (plugin/updates.go), so there is one range grammar in filex, not two. ok is true on a development build (ranges are not enforced there) and for an empty range; err when the range does not parse.

func LocalizeNote

func LocalizeNote(note, lang string) string

LocalizeNote reads a schedule row's note in `lang`: a note written by wakeNote is composed in that language; a {lang: …} object (a row written before the record existed) is read the same way; anything else (an error) is returned as it is.

func Matches

func Matches(a wire.Applies, items []Item) bool

Matches reports whether an Applies rule accepts a selection described by (kind, ext, mime) per item. It is the one implementation the server uses for every check; the explorer mirrors it client-side for the menu.

func NewDocumentKey added in v0.48.0

func NewDocumentKey(plugin, ext string) string

NewDocumentKey is the key a row is asked for by.

func NewJobID

func NewJobID() string

NewJobID mints the random token pending_ops.dest carries.

func NextTickBoundary

func NextTickBoundary(now time.Time) time.Time

NextTickBoundary is when the wake-up after now falls: the next boundary of TickInterval, in UTC. Boundaries rather than "an hour from whenever it last ran" so a restart at 03:37 does not move every later wake-up to :37.

func OutputOverride

func OutputOverride(params map[string]any) *wire.Output

OutputOverride reads the per-job output stored in params, or nil.

func ParseCatalogue

func ParseCatalogue(b []byte) ([]string, error)

ParseCatalogue reads the embedded catalogue file (a flat key → English object — the exact shape of one `ui_locales` language) into its keys.

func PersonalStateKeys

func PersonalStateKeys(keys []string, userID int64) []string

PersonalStateKeys is the view of a file's state keys (`<plugin>:<key>`) that ONE person gets: a personal key `<key>@<id>` (wire.PersonalState) is that person's own and reads `<key>@me`; anybody else's is left out, so a listing never says who else has something to do on the file. Other keys pass as they are. The listing (app badges) and the run check (authorise) both read the state through this, so the menu and the server agree.

func PublicURL

func PublicURL(base, token string) string

PublicURL builds the visitor link; base is the instance's public URL.

func SanitizeSurface

func SanitizeSurface(s *wire.Surface)

SanitizeSurface prunes unknown node types, caps counts and depth.

func SetOutputOverride

func SetOutputOverride(params map[string]any, o *wire.Output)

SetOutputOverride stores o under the override key.

func StripHostParams added in v0.48.0

func StripHostParams(params map[string]any) map[string]any

StripHostParams is params without the host's own keys, never nil.

⚠⚠ runJob obeys these without asking again, so they may only carry what a door checked: the output-folder validation (the storage's tenant, the person's editor right there, filex's own folders) lives at submit. Every door that queues a job — the run request, a surface's job, a public page's job, a scheduled item — strips them first and then stamps only its own.

func TickTimeout

func TickTimeout(m *Manifest) int

TickTimeout is the clamped budget for one `tick` call: whatever the manifest asked for as a call timeout, never more than MaxTickTimeout.

func UIPolicy added in v0.48.0

func UIPolicy(grants Grants, pkg string) (csp, allowlist string)

UIPolicy is the Content-Security-Policy and the Connection-Allowlist an interface's HTML is served with. `pkg` is <P>: the explicit origin and version path the interface is served from, ending in "/" (https://files.example.com/_appui/drawio/3f9a0c1e5b2d4f60/). `grants` is what the administrator approved — the ONLY source of exceptions.

func ValidOutputMode

func ValidOutputMode(m string) bool

ValidOutputMode is the closed set an Output.Mode may take. `folder` is a per-job choice only (a manifest declares `sibling` + `elsewhere`).

func ValidScheduleKey

func ValidScheduleKey(key string) bool

ValidScheduleKey reports whether an app may use key for a scheduled item.

func WithActorIP

func WithActorIP(ctx context.Context, ip string) context.Context

WithActorIP carries the address a signed-in person's request came from into a view call, where it becomes `context.actor.ip` (wire.Actor.IP). The handler knows the request (and the trusted-proxy rule that reads it); the registry does not, and should not have to.

func WithScope

func WithScope(ctx context.Context, s *Scope) context.Context

WithScope attaches a call scope to ctx.

Types

type ActionRow

type ActionRow struct {
	Plugin  string       `json:"plugin"`
	ID      string       `json:"id"`
	Key     string       `json:"key"`
	Label   wire.Text    `json:"label"`
	Icon    string       `json:"icon,omitempty"`
	Applies wire.Applies `json:"applies"`
	View    string       `json:"view,omitempty"`
	// ViewPlacement is the declared placement of View (modal | page), so
	// the menu knows whether to open a dialog or a new tab.
	ViewPlacement string    `json:"view_placement,omitempty"`
	Confirm       wire.Text `json:"confirm,omitempty"`
	MinRole       string    `json:"min_role,omitempty"`
	Danger        bool      `json:"danger,omitempty"`
	OutputMode    string    `json:"output_mode"`
	// OutputElsewhere: the result may go into a folder the person chooses
	// (manifest output.elsewhere), so the action is offered on a read-only
	// storage too — its screen asks where the result should go.
	OutputElsewhere bool `json:"output_elsewhere,omitempty"`
	// Gated is what the action WOULD also be offered on once a requirement
	// the server lacks is met — the extensions LibreOffice would add to the
	// signing app's "Sign…", say. Sent to administrators only: the owner's
	// rule for anything that depends on how the server is set up is "greyed
	// with the reason for an administrator, not shown to anybody else", so
	// the explorer draws each as a disabled row that says what is missing.
	Gated []GatedRule `json:"gated,omitempty"`
	// UI: the action opens the app's own interface (its view has a `ui`
	// file) — the explorer draws the frame; nothing is queued.
	UI *UIRef `json:"ui,omitempty"`
}

ActionRow is one menu entry as GET /api/files/plugins/actions returns it.

type ActionsAnswer

type ActionsAnswer struct {
	Actions []ActionRow `json:"actions"`
	Views   []ViewRow   `json:"views"`
}

ActionsAnswer is the whole list for one caller.

type AppNewDocument added in v0.48.0

type AppNewDocument struct {
	Key      string    `json:"key"`
	Plugin   string    `json:"plugin"`
	View     string    `json:"view"`
	Ext      string    `json:"ext"`
	Label    wire.Text `json:"label"`
	Mime     string    `json:"mime"`
	Template bool      `json:"template"`
}

AppNewDocument is one row an app adds to the "New" menu, as the explorer is told it. Key is what a create request names (`app:<plugin>:<ext>`).

type AutoUpdated added in v0.47.0

type AutoUpdated struct {
	From string    `json:"from"`
	To   string    `json:"to"`
	At   time.Time `json:"at"`
}

AutoUpdated is one automatic update: from which version, to which, when.

type CAList

type CAList struct {
	ID          string     `json:"id"`
	Subject     string     `json:"subject"`
	Issuer      string     `json:"issuer"`
	Fingerprint string     `json:"fingerprint"`
	Imported    bool       `json:"imported"`
	NotBefore   time.Time  `json:"not_before"`
	NotAfter    time.Time  `json:"not_after"`
	CreatedAt   time.Time  `json:"created_at"`
	RetiredAt   *time.Time `json:"retired_at,omitempty"`
	Live        bool       `json:"live"`
}

CAList describes one signing authority for the admin surface.

type CallError

type CallError struct {
	Code    string
	Export  string
	Message string // safe for a user; the guest's own words when it set any
	Cause   error  // the runtime's error, for the plugin log only
}

CallError is a failed guest call, classified.

func (*CallError) Error

func (e *CallError) Error() string

func (*CallError) Unwrap

func (e *CallError) Unwrap() error

type Cataloguer

type Cataloguer interface {
	Catalogue(ctx context.Context, storageID int64, rel string) (*model.Node, error)
}

Cataloguer records in the catalogue a file the STORAGE has and the catalogue has not seen yet, with the same bookkeeping a write through filex gets (node row, parent rows, index, thumbnail), and answers its node.

The output sink implements it (handlers.AppPlugins, through protocolsync); it is a separate interface so a sink that does not — every test double of OutputSink — leaves share_create exactly as strict as it was.

type Compat added in v0.47.0

type Compat struct {
	Requires string `json:"requires"`
	OK       bool   `json:"ok"`
	Filex    string `json:"filex"`
}

Compat is what the Apps screens say about an app's range: the range as written, whether the running filex is in it, and which filex that is. Absent when the manifest names none.

type Compiled

type Compiled struct {
	Name     string
	Manifest *Manifest
	SHA256   string
	// contains filtered or unexported fields
}

Compiled is one installed plugin's module, compiled once and instantiated per call.

func (*Compiled) Call

func (c *Compiled) Call(ctx context.Context, export string, input []byte, budget time.Duration) ([]byte, error)

Call runs one export on a fresh instance with a wall-clock budget. The returned bytes are the guest's output. Errors are *CallError.

ctx may carry a call scope (WithScope) that host functions read to decide what this particular call is allowed to touch; the guest never sees it.

func (*Compiled) Close

func (c *Compiled) Close(ctx context.Context) error

Close frees the compiled module.

func (*Compiled) Describe

func (c *Compiled) Describe(ctx context.Context, locale string) (*wire.Manifest, error)

Describe calls the guest's describe export and checks that it agrees with the installed manifest on the things a grant depends on. The file on disk is the operator's intent; the guest's answer is the proof it is the same program.

func (*Compiled) HasExport

func (c *Compiled) HasExport(ctx context.Context, export string) (bool, error)

HasExport reports whether the module exports a function by that name.

It costs one instantiation, which is why it is asked at load rather than per call: the alternative is discovering an hour later, in a log, that an app which asked to be woken has nothing to wake.

type DryRunAnswer

type DryRunAnswer struct {
	Manifest    *wire.Manifest  `json:"manifest"`
	Permissions []PermissionRow `json:"permissions"`
	// WasmSHA256 / WasmBytes describe the module; both empty for a language
	// pack, which has none (ManifestSHA256 is what was verified instead).
	WasmSHA256 string `json:"wasm_sha256"`
	WasmBytes  int64  `json:"wasm_bytes"`
	Signed     bool   `json:"signed"`
	// Kind: KindApp | KindLanguagePack. The review says which BEFORE the
	// install, so an administrator is not asked to trust a module that is
	// not there, or surprised by one that is.
	Kind           string `json:"kind"`
	ManifestSHA256 string `json:"manifest_sha256,omitempty"`
	// Languages: what the app would add to filex itself — coverage of the
	// current catalogue, and whether each is written right to left.
	Languages []LanguageRow `json:"languages,omitempty"`
	// Installed is the app of the same name already on this server (an
	// install cannot proceed; an upgrade of it can). Nil on an upgrade's own
	// dry run, and when nothing of that name is installed.
	Installed *DryRunInstalled `json:"installed,omitempty"`
	// EnginesMissing are the engines the manifest asks for that this server
	// does not have: the app installs, and whatever needs them will not work.
	EnginesMissing []DryRunEngine `json:"engines_missing,omitempty"`
	// Compat is the manifest's `filex` range judged against this filex
	// (compat.go); absent when it names none. `ok: false` is said HERE, at
	// the review, and the install or upgrade itself answers `incompatible`.
	Compat *Compat `json:"compat,omitempty"`
	// Upgrade is what an upgrade's review shows on top of an install's: the
	// version it replaces and how the grant changes. Nil on an install.
	Upgrade *DryRunUpgrade `json:"upgrade,omitempty"`
	// Engine: the app brings a WebAssembly module (false: a language pack, or
	// an app that is only an interface).
	Engine bool `json:"engine"`
	// UI is the app's own interface: the bundle, its script-policy
	// exceptions, and every external address — mirrored or live. The review
	// draws it as its own group, with what a live address means.
	UI *UIInfo `json:"ui,omitempty"`
}

DryRunAnswer is what a dry run returns for the review step.

⚠ Installed and EnginesMissing are said HERE, at the review, because the dry run already knows them. Before, reinstalling an app that was already there passed the review and only "Install" answered "an app with this name is already installed", and an app needing an engine this server lacks was reviewed as if it would work (release-candidate sweep, 2026-09-21).

type DryRunEngine

type DryRunEngine struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

DryRunEngine is one engine by its id and by the name a person reads.

type DryRunInstalled

type DryRunInstalled struct {
	ID      int64  `json:"id"`
	Version string `json:"version"`
}

DryRunInstalled names the installed app a new install would collide with.

type DryRunUpgrade added in v0.47.0

type DryRunUpgrade struct {
	From       string   `json:"from"`
	Added      []string `json:"added,omitempty"`
	Removed    []string `json:"removed,omitempty"`
	AddsModule bool     `json:"adds_module,omitempty"`
	// What else the version changes, for the review (upgradeReview): the
	// module (its hash), the interface (added, removed, its files), the filex
	// range, the signature, and the source's own notes. The approval rule
	// itself is Added/AddsModule and nothing else.
	ModuleFrom string      `json:"module_from,omitempty"`
	ModuleTo   string      `json:"module_to,omitempty"`
	UIFrom     string      `json:"ui_from,omitempty"`
	UITo       string      `json:"ui_to,omitempty"`
	UIFiles    *UIFileDiff `json:"ui_files,omitempty"`
	FilexFrom  string      `json:"filex_from,omitempty"`
	FilexTo    string      `json:"filex_to,omitempty"`
	SignedFrom bool        `json:"signed_from"`
	SignedTo   bool        `json:"signed_to"`
	Notes      string      `json:"notes,omitempty"`
}

DryRunUpgrade is the jump an upgrade makes. Added are the permissions the new version asks for that the installed one was not granted — what the administrator is asked to approve; Removed are the ones it no longer asks for (the grant narrows by itself). AddsModule: a language pack that now brings a module — code that runs, where there was none.

type GatedRule

type GatedRule struct {
	Ext   []string `json:"ext,omitempty"`
	Needs Need     `json:"needs"`
}

GatedRule is one "offered once X is there" part of an action's rule.

type GitHubInput

type GitHubInput struct {
	Repo string `json:"github_repo"`
	Ref  string `json:"ref"`
}

GitHubInput is the JSON body of a GitHub install.

type Grants

type Grants map[Permission]bool

Grants is the set an administrator approved.

func NewGrants

func NewGrants(perms []Permission) Grants

NewGrants builds a set from validated permissions.

func (Grants) AllowedHosts

func (g Grants) AllowedHosts() []string

AllowedHosts lists the http:* grants as Extism allowed_hosts patterns.

func (Grants) Has

func (g Grants) Has(p Permission) bool

Has answers a bare permission.

func (Grants) HasEngine

func (g Grants) HasEngine(name string) bool

HasEngine answers engines:<name>.

func (Grants) HasHost

func (g Grants) HasHost(host string) bool

HasHost answers whether an outbound HTTP request to host is allowed: an exact "http:host" grant, or a "http:*.domain" grant covering a subdomain.

func (Grants) Missing

func (g Grants) Missing(want []Permission) []Permission

Missing returns the permissions in want that are not in g — what an upgrade must ask the administrator to approve again.

type HostFunc

type HostFunc = extism.HostFunction

HostFunc is a host function the runtime links into every instance. The registry builds them (hostfn.go); tests pass a few of their own.

type InstallError

type InstallError struct {
	Code    string
	Message string
	Missing []string
	Reason  string   // a FetchReason* for fetch_failed, else ""
	Where   string   // the repository or the URL that was being fetched
	Refs    []string // the refs a repository install tried
	Status  int      // the HTTP status that came back, when one did
	// Requires and Filex narrow an `incompatible`: the range the manifest
	// declares and the filex it leaves out (compat.go).
	Requires string
	Filex    string
}

InstallError is a failure caused by what the caller supplied. Code is what the HTTP layer answers; Missing carries the permission diff for permissions_incomplete / permissions_changed.

Reason, Where, Refs and Status narrow a fetch_failed, so the install wizard can write a sentence in the reader's language that says WHAT to check. ⚠ The message alone used to be the answer: "filex-app.json not found in BRF-Tech/yok-boyle-bir-depo: http 404 from raw.githubusercontent.com", English, in the Turkish wizard (release-candidate sweep, 2026-09-21).

func (*InstallError) Error

func (e *InstallError) Error() string

type InstallInput

type InstallInput struct {
	Manifest []byte
	// Wasm is the module; Reader is consumed, capped at MaxWasmBytes.
	Wasm io.Reader
	// UI is the interface bundle (a zip) when the manifest has a `ui` block;
	// consumed, capped at MaxUIBytes.
	UI io.Reader
	// SHA256 (lower hex) the module must match; "" skips (upload only).
	SHA256    string
	Signature string
	Source    string
	SourceURL string
	// ManifestURL is where a URL install read the manifest — what the update
	// check re-reads (SourceURL is the module's address for an app).
	ManifestURL string
	// Pinned: the administrator gave the SHA-256 themselves (a URL install).
	// "Exactly these bytes" is not something an automatic update may
	// overrule, so such an install starts with automatic updates off.
	Pinned bool
	// Granted is the permission list the admin approved. It must equal the
	// manifest's set exactly.
	Granted []string
	DryRun  bool
	Lang    string
	// ActorID is the administrator who approved this (the audit row names
	// them); nil for the system.
	ActorID *int64
	// Rollback: this is "Back to <version>" (versions.go) — the kept files,
	// under the grant they ran with.
	Rollback bool
	// UIPin pins the interface bundle when the manifest does not (a kept
	// version is held to the hash recorded when it was replaced).
	UIPin string
	// Mirrors are mirrored external files already at hand, by sha256 (a kept
	// version's): used instead of downloading them again.
	Mirrors map[string][]byte
	// Notes are the source's release notes for this version, as plain text,
	// for the review (a GitHub release's body).
	Notes string
}

InstallInput is one install or upgrade request after the HTTP layer has gathered the pieces.

type InstallRefusal added in v0.47.0

type InstallRefusal struct {
	Code     string   `json:"error"`
	Message  string   `json:"message,omitempty"`
	Missing  []string `json:"missing,omitempty"`
	Reason   string   `json:"reason,omitempty"`
	Where    string   `json:"where,omitempty"`
	Refs     []string `json:"refs,omitempty"`
	Status   int      `json:"status,omitempty"`
	Requires string   `json:"requires,omitempty"`
	Filex    string   `json:"filex,omitempty"`
}

InstallRefusal is an InstallError as the wire carries it — the body of a refused install (handlers.installErrorBody) and the reason an update check or an automatic update stopped. One shape, so the panel explains both with the same sentences.

func RefusalOf added in v0.47.0

func RefusalOf(err error) *InstallRefusal

RefusalOf is err as the panel reads it.

type Installed

type Installed struct {
	Row      *model.AppPlugin
	Manifest *Manifest
	Grants   Grants
	// Perms is the grant as a sorted list, for answers.
	Perms []Permission
	// contains filtered or unexported fields
}

Installed is one plugin's live entry.

func (*Installed) HasModule added in v0.48.0

func (p *Installed) HasModule() bool

HasModule reports whether the app runs a wasm module: every app but a language pack and an app that is only an interface (its row has no module file).

func (*Installed) State

func (p *Installed) State() (string, string)

State returns the live state and its error.

func (*Installed) UI added in v0.48.0

func (p *Installed) UI() *uiBundle

UI is the app's loaded interface, nil when it has none (or it failed to load — the state says why).

func (*Installed) UIBundle added in v0.48.0

func (p *Installed) UIBundle(short string) *uiBundle

UIBundle is the interface version an address names (its bundle sha256's first 16 hex digits); nil when this app serves no such version.

type Item

type Item struct {
	Kind string // file | dir
	Ext  string // lower-case, no dot
	Mime string
	// State is the set of state keys the plugin under consideration keeps
	// on this item (bare keys). Filled per plugin by the caller.
	State []string
}

Item is what Matches sees of a selected entry.

func (Item) HasState

func (it Item) HasState(key string) bool

HasState reports whether the item carries key.

type JobOutput

type JobOutput struct {
	Path string `json:"path"`
}

JobOutput is one committed output, as the ops row reports it.

type JobQueue

type JobQueue interface {
	SubmitTo(ctx context.Context, kind string, storageID, destStorageID int64, sources []string, dest string) (*ops.Op, error)
}

JobQueue is the ops queue, as the scheduler needs it: a way to hand an app_plugin_jobs row to the same worker a person's job goes through. *ops.Service satisfies it; the server injects it with SetJobQueue.

Without it a due item fails loudly (status `failed`, the reason on the row) rather than disappearing.

type LanguageRow

type LanguageRow struct {
	Code string `json:"code"`
	// Keys the pack carries for this language (empty values not counted).
	Keys int `json:"keys"`
	// Translated is how many of those the current catalogue has; Unknown is
	// the rest — keys filex no longer has, or never had (typos). Both are
	// only meaningful when Total > 0.
	Translated int `json:"translated"`
	Unknown    int `json:"unknown"`
	// Total is the size of the current catalogue; 0 means this binary was
	// built without one (a development build), and coverage is unknown.
	Total int `json:"total"`
	// Percent is floor(100 × Translated / Total): 100 only when nothing is
	// missing, so "100 %" is never a rounding of "almost".
	Percent int `json:"percent"`
	// RTL: a right-to-left language (wire.IsRTL) — the interface is laid out
	// right to left in it.
	RTL bool `json:"rtl"`
}

LanguageRow is one language an app adds, as the Apps screens show it.

⚠ Coverage is measured against the CURRENT catalogue — the one this very binary's interface draws from — not against whatever the pack's author had when they wrote it. A pack written for v0.43 and run on v0.45 says how much of v0.45 it covers, and every string it lacks falls back to English.

type LogLine

type LogLine struct {
	Seq   int64     `json:"seq"`
	TS    time.Time `json:"ts"`
	Level string    `json:"level"`
	Msg   string    `json:"msg"`
}

LogLine is one entry of a plugin's ring buffer: what the guest logged through pdk.Log plus the host's own warnings about it (refused calls, failed loads). The admin panel polls it with ?after=<seq>.

type Manifest

type Manifest struct {
	wire.Manifest
	// Perms is Permissions parsed and validated, followed by the permissions
	// the `ui` block derives (uimanifest.go).
	Perms []Permission
	// contains filtered or unexported fields
}

Manifest is a validated filex-app.json.

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

ParseManifest decodes and validates a manifest document.

func (*Manifest) Action

func (m *Manifest) Action(id string) (*wire.Action, bool)

Action finds an action by id.

func (*Manifest) CallTimeout

func (m *Manifest) CallTimeout() int

CallTimeout is the clamped per-call budget for describe/view/page calls.

func (*Manifest) Grants

func (m *Manifest) Grants() Grants

Grants is the full permission set the manifest asks for.

func (*Manifest) MemoryPages

func (m *Manifest) MemoryPages() int

MemoryPages is the clamped wasm memory ceiling.

func (*Manifest) NeedsModule added in v0.48.0

func (m *Manifest) NeedsModule() bool

NeedsModule reports whether anything the manifest declares runs a module: an action that does not just open an interface, a view the module draws, a public page, or a permission only a module can use. A manifest with an interface and none of these may install without a module (an app that is ONLY an interface, like draw.io's editor). A language pack never needs one.

func (*Manifest) UIViews added in v0.48.0

func (m *Manifest) UIViews() map[string]*wire.View

UIViews is every view of m that the interface draws, by id.

func (*Manifest) Validate

func (m *Manifest) Validate() error

Validate checks the shape and fills Perms.

func (*Manifest) View

func (m *Manifest) View(id string) (*wire.View, bool)

View finds a view by id.

func (*Manifest) WritesElsewhere

func (m *Manifest) WritesElsewhere() bool

WritesElsewhere reports whether any action may put its result into a folder the person chooses (output.elsewhere) — what makes the host tell the app where that person's results go by default (CallContext.Home).

type Need

type Need struct {
	Kind string `json:"kind"`
	ID   string `json:"id"`
	Name string `json:"name"`
}

Need is a requirement the server does not meet: `kind` says which sort ("engine" today), `id` the machine's word, `name` the one a person reads.

type Options

type Options struct {
	Store db.Store
	// Share mints the public links app plugins open. An app's public page IS
	// a share (migration 00046), so it goes through the same service every
	// other link does and inherits the instance's expiry ceiling, the PIN
	// gate and the administrator's revoke. nil refuses share_create.
	Share *share.Service
	// Dir is where modules live; the compilation cache and the call spool
	// sit next to it.
	Dir string
	// SecretKey seals secret settings (secretbox). Empty → secret fields are
	// refused at PutSettings rather than stored in the clear.
	SecretKey string
	// TrustedKeys (hex/base64 ed25519) make a detached signature mandatory.
	TrustedKeys []string
	// Demo refuses every install/upgrade (the admin account is public).
	Demo bool
	// HTTP fetches manifests and modules for URL/GitHub installs.
	HTTP *http.Client
	Log  *slog.Logger
	// StorageResolver opens the driver a job reads from and writes to.
	StorageResolver func(int64) (storage.Driver, error)
	// Limits (bytes). Zero → defaults below.
	MaxWasmBytes   int64
	MaxInputBytes  int64
	MaxOutputBytes int64
	// MaxUIBytes caps an interface bundle, zipped (FILEX_APP_PLUGIN_MAX_UI_MB).
	MaxUIBytes int64
	// PerPluginJobs bounds concurrent action_run calls per plugin (default 2).
	PerPluginJobs int
	// PerPluginCalls bounds concurrent SCREEN calls per plugin — view and
	// public page events (default 8). Each is a fresh instance with up to
	// the manifest's memory ceiling (256 MiB at most), and a public page
	// event is answered for an anonymous visitor: without a ceiling, a burst
	// of events on one link held that much memory per request.
	PerPluginCalls int
}

Options configures the registry.

type OutputSink

type OutputSink interface {
	// CommitSibling writes a NEW file next to the first input, taking a
	// unique name when the preferred one is taken. Returns the final path.
	CommitSibling(ctx context.Context, storageID int64, dir, name string, r io.Reader, size int64, actor *int64) (string, error)
	// CommitVersion overwrites rel in place (the previous bytes become a
	// version when versioning is on).
	CommitVersion(ctx context.Context, storageID int64, rel string, r io.Reader, size int64, actor *int64) error
}

OutputSink lands a finished output on a storage with filex's own bookkeeping (kind guard, version snapshot on overwrite, node row, index, thumbnail, write hook, change frame). Implemented by the HTTP layer, which owns those services; the registry only decides WHAT to write WHERE.

type OverrideRow

type OverrideRow struct {
	ID        string        `json:"id"`
	Enabled   bool          `json:"enabled"`
	AdminOnly bool          `json:"admin_only"`
	Applies   *wire.Applies `json:"applies"`
}

OverrideRow is the admin API shape of one action override. Applies is a WHOLE rule both ways — the editor shows a list and sends a list — while the row stores only what changed against the manifest (applies_override.go says why). Nil = as the manifest says.

type PageFile

type PageFile struct {
	Ref  string `json:"ref"`
	Name string `json:"name"`
	File string `json:"file"`
	Size int64  `json:"size"`
	Mime string `json:"mime,omitempty"`
}

PageFile is one exposed copy, as files_json stores it.

⚠ `File` is a BASENAME inside the share's own directory, not an absolute path. v2 stored the absolute path, so moving or restoring the data directory silently broke every exposed copy while the row looked healthy.

type PageInfo

type PageInfo struct {
	Plugin      string         `json:"plugin"`
	Page        string         `json:"page"`
	Title       wire.Text      `json:"title"`
	Subject     string         `json:"subject,omitempty"`
	RequiresPIN bool           `json:"requires_pin"`
	Unlocked    bool           `json:"unlocked"`
	ExpiresAt   *time.Time     `json:"expires_at,omitempty"`
	Files       []wire.FileRef `json:"files"`
}

PageInfo describes an app link without opening it.

type Permission

type Permission string

Permission is one capability a plugin asks for in its manifest and an administrator grants at install. The set is CLOSED: a manifest naming a permission this host does not know is refused at install, because a grant the admin cannot read is not a grant.

Two shapes: bare names ("files:read") and parameterised ones ("http:api.example.com", "engines:ffmpeg"). The parameter is part of the permission — granting "http:a.example.com" says nothing about b.example.com.

const (
	PermFilesRead   Permission = "files:read"
	PermFilesWrite  Permission = "files:write"
	PermFilesLock   Permission = "files:lock"
	PermSign        Permission = "sign"
	PermMailSend    Permission = "mail:send"
	PermNotifySend  Permission = "notify:send"
	PermUsersLookup Permission = "users:lookup"
	PermSettings    Permission = "settings"
	PermState       Permission = "state"
	PermPublicPages Permission = "public_pages"
	// PermSchedule is the one permission that makes an app run when nobody
	// asked it to: it is woken once an hour and runs the work it scheduled
	// at the minute it named. An app without it is never woken, and there
	// is no other way in — see schedule.go.
	PermSchedule Permission = "schedule"
	// The interface's own permissions (uiperm.go) — DERIVED from the
	// manifest's `ui` block, never listed in `permissions`: `ui` for having
	// an interface at all, `ui:eval` / `ui:wasm-eval` for its script-policy
	// exceptions. Live addresses are `ui-net:<as>:<url>`.
	PermUI         Permission = "ui"
	PermUIEval     Permission = "ui:eval"
	PermUIWasmEval Permission = "ui:wasm-eval"
	// PermUIPackageFetch: the interface reads its own package — this
	// version's files, nothing else (`ui.package_fetch`).
	PermUIPackageFetch Permission = "ui:package-fetch"
	// PermUIDownload: the interface hands the person files to keep on their
	// own disk, each time with their say (`ui.download`).
	PermUIDownload Permission = "ui:download"
)

Bare permissions.

func ParsePermission

func ParsePermission(s string) (Permission, error)

ParsePermission validates one manifest entry.

func UINetPermission added in v0.48.0

func UINetPermission(as, u string) Permission

UINetPermission is the permission a live address is: `ui-net:<as>:<url>`.

func (Permission) Label

func (p Permission) Label(lang string) string

Label is the plain-language meaning shown at install, in lang — any language the server catalogue speaks, a language pack's included (keys `server.perm.*`, internal/srvtext). It serves the API (`GET /api/admin/ app-plugins/{id}` → permission review) and the CLI.

⚠ This is what an administrator reads before trusting an app with their files. It was English or Turkish — a Spanish administrator read the one screen that asks for consent in English, around a Spanish wizard. A key a pack lacks falls back to English, never to the permission id.

type PermissionRow

type PermissionRow struct {
	ID     string    `json:"id"`
	Label  string    `json:"label"`
	Reason wire.Text `json:"reason,omitempty"`
}

PermissionRow is one line of the install-time review.

func PermissionRows

func PermissionRows(m *Manifest, lang string) []PermissionRow

PermissionRows renders the review for a manifest in lang.

type PreviousVersion added in v0.48.0

type PreviousVersion struct {
	Version    string    `json:"version"`
	ReplacedAt time.Time `json:"replaced_at"`
	// UI: the kept version has an interface of its own.
	UI bool `json:"ui,omitempty"`
}

PreviousVersion is the kept version an administrator can go back to.

type Registry

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

Registry is the installed set plus the runtime.

func New

func New(o Options) (*Registry, error)

New prepares the registry (no plugin is loaded until Load).

func (*Registry) ActionsFor

func (r *Registry) ActionsFor(ctx context.Context, isAdmin bool) (*ActionsAnswer, error)

ActionsFor lists what the caller may see: running plugins, enabled actions, admin-only ones only for admins.

func (*Registry) All

func (r *Registry) All() []*Installed

All returns every entry, by name. (The languages the running apps add to filex itself — UILocaleList / UILocale — live in langpack.go.)

func (*Registry) AppLabel

func (r *Registry) AppLabel(name string) wire.Text

AppLabel is an installed app's own label, in every language its manifest wrote it in ({"en": "e-Signature", "tr": "e-İmza"}), or nil when no app of that name is installed here.

⚠ A lock, an action row and a job all carry the app's manifest NAME (`sign`), because that is what addresses it; a person reading "sign locked this file" is reading an identifier the rest of the product never shows them — every list, the install review and the app's own page say "e-Signature" (v0.43.0). So the words a person reads resolve the name to this, and fall back to the name only where the app is gone.

func (*Registry) BackgroundUpdates added in v0.47.0

func (r *Registry) BackgroundUpdates() bool

BackgroundUpdates reports whether the daily check runs.

func (*Registry) ByID

func (r *Registry) ByID(id int64) (*Installed, bool)

ByID returns the entry for a row id.

func (*Registry) ByName

func (r *Registry) ByName(name string) (*Installed, bool)

ByName returns the entry for a plugin name.

func (*Registry) CACertPEM

func (r *Registry) CACertPEM(ctx context.Context, tenantID int64) (string, error)

CACertPEM returns the tenant CA certificate (creating the CA on first use).

func (*Registry) CACertsPEM

func (r *Registry) CACertsPEM(ctx context.Context, tenantID int64) (string, error)

CACertsPEM returns EVERY certificate this tenant has ever signed with — the live CA first, then the retired ones, newest first — as one PEM bundle. This is the root set a verifier needs: a signature made two CAs ago must still check out, so nothing here is ever deleted.

func (*Registry) CAs

func (r *Registry) CAs(ctx context.Context, tenantID int64) ([]CAList, error)

CAs lists the tenant's signing authorities, live and retired, for the admin panel and the verify screen: who signed, with what, until when.

func (*Registry) CheckPIN

func (r *Registry) CheckPIN(ctx context.Context, sh *model.Share, pin string) error

CheckPIN verifies a PIN with the strike counter. A one-line forward: the gate itself is internal/share/pin.go, so /s/, /d/ and an app page cannot drift apart — which is exactly what v2 did (only the app page had a lock).

func (*Registry) CheckUpdates added in v0.47.0

func (r *Registry) CheckUpdates(ctx context.Context) (*UpdateReport, error)

CheckUpdates runs one check of every app that has a source and records what it found (it installs nothing) — or, when a check is already running, waits for that one and answers what it found. One at a time, whoever asks.

func (*Registry) Close

func (r *Registry) Close(ctx context.Context)

Close frees every compiled module and the runtime. It waits for an upgrade in flight — an automatic one included — to finish its swap, and refuses any after it, so a shutdown never leaves an app half-replaced.

func (*Registry) DecorateOps

func (r *Registry) DecorateOps(ctx context.Context, rows []*ops.Op)

DecorateOps fills the plugin fields of ops rows that are plugin jobs. DecorateOps fills the plugin columns of ops rows. A read failure is logged rather than swallowed: the row still renders, but as a nameless "plugin-action" with no message, which is how two different lost-update bugs stayed invisible.

func (*Registry) Dir

func (r *Registry) Dir() string

Dir is where modules live.

func (*Registry) Engines

func (r *Registry) Engines() map[string]bool

Engines is the engine → installed map for this host.

func (*Registry) FetchGitHub

func (r *Registry) FetchGitHub(ctx context.Context, in GitHubInput) (*InstallInput, error)

FetchGitHub resolves a repository + ref into an InstallInput (manifest and module fetched, sha256 taken from the manifest unless overridden).

func (*Registry) FetchURL

func (r *Registry) FetchURL(ctx context.Context, in URLInput) (*InstallInput, error)

FetchURL resolves explicit module + manifest addresses.

func (*Registry) FetchUpdate added in v0.47.0

func (r *Registry) FetchUpdate(ctx context.Context, id int64) (*InstallInput, error)

FetchUpdate reads the newer version an app's source has — the one the update check announced — for an administrator's review and upgrade ("Review update" on the Apps list). The same search the check makes, so what is reviewed is what was announced; the decision is the administrator's.

func (*Registry) ImportCA

func (r *Registry) ImportCA(ctx context.Context, tenantID int64, certPEM, keyPEM []byte) (*signingKey, error)

ImportCA takes over signing with an authority the operator already has — their own PKI, or a certificate they trust everywhere else. The current CA is retired, not deleted, so everything it signed keeps verifying.

certPEM may be a bundle: the signing certificate first, its issuers after it. keyPEM is that certificate's private key, in PKCS#8, PKCS#1 or SEC1 form, NOT encrypted — a .p12/.pfx is converted first (`openssl pkcs12 -in ca.p12 -nodes -out ca.pem`), because the encrypted forms in the wild are more varied than any one library reads.

func (*Registry) Install

func (r *Registry) Install(ctx context.Context, in *InstallInput) (*Status, *DryRunAnswer, error)

Install stages, reviews and installs a plugin.

func (*Registry) InstanceID

func (r *Registry) InstanceID() string

InstanceID is what this process writes into claimed_by.

func (*Registry) LanguageRows

func (r *Registry) LanguageRows(m *Manifest) []LanguageRow

LanguageRows describes every language m adds, with its coverage. Nil-safe on the receiver (no catalogue → coverage unknown).

func (*Registry) LastUpdateCheck added in v0.47.0

func (r *Registry) LastUpdateCheck(ctx context.Context) time.Time

LastUpdateCheck is when the last check ran; zero when never.

func (*Registry) LinkOf

func (r *Registry) LinkOf(sh *model.Share) *db.AppLink

LinkOf says what an app's link IS, for a list of links (My shares, the admin's Shares): the purpose the app gave the link when it opened it, else the one its page declares, and the app's home view the row opens. Nil for an ordinary share, for an app that is gone, and for a link with no purpose — the row then lists as a plain share.

⚠ Page-less links too (an app's plain share of a file: PluginID set, no PageID). The finished document's delivery link listed as a share somebody made by hand, beside signing links marked as what they are.

func (*Registry) Load

func (r *Registry) Load(ctx context.Context) error

Load reads every row and compiles the enabled ones. A row that fails to load is kept (state failed/refused) so the admin sees why; it never stops the server.

func (*Registry) LoadPage

func (r *Registry) LoadPage(ctx context.Context, token string) (*model.Share, *Installed, error)

LoadPage resolves a token to the live app share behind it (ErrPageNotFound / ErrPageGone).

⚠ A token that names an ordinary share is ErrPageNotFound here, not an error about kinds: this is an unauthenticated lookup and "wrong sort of link" is a fact about somebody else's data.

func (*Registry) LockReason

func (r *Registry) LockReason(l *model.AppPluginLock) (string, wire.Text)

LockReason is a lock's reason as words: the plain text (English, for a consumer with no reader — a log, a protocol server) and, for a reason the app named by message key, the same words in every language the app wrote them in. A plain reason has no Text; a message whose app or key is gone has no words at all (never the raw key).

func (*Registry) Locks

func (r *Registry) Locks(ctx context.Context, storageID int64) ([]*model.AppPluginLock, error)

Locks lists every live app-plugin lock (admin surface). storageID 0 = all.

func (*Registry) Logs

func (r *Registry) Logs(id int64, after int64) ([]LogLine, int64, error)

Logs returns the ring lines after seq.

func (*Registry) LookupUsers

func (r *Registry) LookupUsers(ctx context.Context, q string) ([]UserRow, error)

LookupUsers is the one directory search behind both the host function and the HTTP route. ctx must already carry the caller's tenant scope (the store is the tenant-scoped wrapper, which confines ListUsers by that scope).

func (*Registry) MaxOutputBytes added in v0.48.0

func (r *Registry) MaxOutputBytes() int64

MaxOutputBytes is the ceiling on one file an app writes (a job's output, an interface's save).

func (*Registry) MaxUIBytes added in v0.48.0

func (r *Registry) MaxUIBytes() int64

MaxUIBytes is the interface bundle's ceiling, zipped.

func (*Registry) MintUnlock

func (r *Registry) MintUnlock(token string) string

MintUnlock / VerifyUnlock forward to the same one implementation.

func (*Registry) NewDocument added in v0.48.0

func (r *Registry) NewDocument(key string) (AppNewDocument, bool)

NewDocument is the row a key names, when it is offered.

func (*Registry) NewDocumentBytes added in v0.48.0

func (r *Registry) NewDocumentBytes(ctx context.Context, key string) ([]byte, error)

NewDocumentBytes is what a new file of the row a key names starts as: the template from the app's package, or nothing. Refused for a row that is not offered (unknown, not granted, the app switched off).

func (*Registry) NewDocuments added in v0.48.0

func (r *Registry) NewDocuments() []AppNewDocument

NewDocuments is every row the running apps add to the "New" menu, app by app in name order and in each app's own order. A row is offered only while the app runs with its interface loaded, and only when the grant holds its kind (`ui-new:.<ext>`).

func (*Registry) Overrides

func (r *Registry) Overrides(ctx context.Context, id int64) ([]OverrideRow, error)

Overrides returns one row per manifest action, merged with stored changes.

func (*Registry) PageAnchor

func (r *Registry) PageAnchor(ctx context.Context, sh *model.Share) (int64, string)

PageAnchor reports the storage and storage-relative path the link is about — what per-file state and the follow-up job hang on. Zero/"" when the node has gone.

func (*Registry) PageEvent

func (r *Registry) PageEvent(ctx context.Context, sh *model.Share, p *Installed, in wire.ViewEventInput, visitorIP string) (*wire.Surface, error)

PageEvent runs the link's page_event export for an unlocked visitor. "open" spends one visit off the share's own counter. A surface carrying a job is returned as-is; the HTTP layer queues it as the share's creator.

func (*Registry) PageFile

func (r *Registry) PageFile(sh *model.Share, ref string) (*PageFile, *os.File, error)

PageFile opens an exposed copy for the visitor (Range-capable).

func (*Registry) PageInfo

func (r *Registry) PageInfo(sh *model.Share, p *Installed, unlocked bool) *PageInfo

PageInfo describes a link without opening it.

func (*Registry) PutOverrides

func (r *Registry) PutOverrides(ctx context.Context, id int64, rows []OverrideRow) error

PutOverrides stores the rows (unknown and hidden action ids dropped, applies validated).

func (*Registry) PutSettings

func (r *Registry) PutSettings(ctx context.Context, id int64, values map[string]string) error

PutSettings stores values. A secret field sent as "***" keeps its current value; other secret values are sealed. Unknown keys are dropped.

func (*Registry) Remove

func (r *Registry) Remove(ctx context.Context, id int64) error

Remove uninstalls a plugin: module, row and everything keyed on it.

func (*Registry) RequiresSignature

func (r *Registry) RequiresSignature() bool

RequiresSignature reports whether installs must carry a signature.

func (*Registry) ResolveAction

func (r *Registry) ResolveAction(ctx context.Context, plugin, action string, isAdmin bool) (*Installed, *wire.Action, wire.Applies, error)

ResolveAction finds a running plugin's enabled action for a run request. isAdmin gates admin-only actions. The error is a *CallError with CodeUnsupported (not running / disabled) or CodeRefused (admin only).

func (*Registry) Rollback added in v0.48.0

func (r *Registry) Rollback(ctx context.Context, id int64, by *int64, lang string) (*Status, error)

Rollback puts the kept version of an app back: its module, manifest, interface and mirrored files, under the grant it ran under — no new approval, because that grant was approved when it was installed. The version it replaces becomes the kept one, so the administrator can go forward again the same way. `by` is the administrator.

func (*Registry) RotateCA

func (r *Registry) RotateCA(ctx context.Context, tenantID int64) error

RotateCA retires the tenant CA; the next signature creates a fresh one. Leaves issued by the old CA stay verifiable through its certificate, which is why retiring never deletes.

func (*Registry) RunDueSchedule

func (r *Registry) RunDueSchedule(ctx context.Context, now time.Time) (int, error)

RunDueSchedule performs ONE pass: every row due at or before now, claimed and run. Exported so a test — and, later, an administrator's "run it now" — can drive the schedule without the loop.

A plugin that traps, panics or hangs takes down its own row and nothing else: each row runs behind a recover, and the call itself is bounded by the wasm runtime's own per-call budget.

func (*Registry) RunPluginAction

func (r *Registry) RunPluginAction(ctx context.Context, op *ops.Op, live func(done, total int64)) error

RunPluginAction is what the ops worker calls for an OpPluginAction row. op.Dest carries the job id. Progress is fed to live so the tray can draw a bar; the job row records status, message and outputs.

func (*Registry) ScheduleOf

func (r *Registry) ScheduleOf(ctx context.Context, id int64) ([]*model.AppPluginScheduleItem, error)

ScheduleOf returns one app's schedule rows, soonest first — what an admin screen would draw and what a test reads back.

func (*Registry) SetCatalogue

func (r *Registry) SetCatalogue(keys []string)

SetCatalogue gives the registry the current string catalogue — every key of the explorer's and the admin panel's English tables — so the Apps screens can say how much of it a language covers.

⚠ Called with the keys of `admin/i18n/filex-catalogue-en.json`, which the web build writes (web/vite.config.ts → scripts/lib/i18n-catalogue.mjs) and the binary embeds. A binary built without the web build has none, and coverage then reads "unknown" rather than a made-up percentage.

func (*Registry) SetEnabled

func (r *Registry) SetEnabled(ctx context.Context, id int64, on bool) (*Status, error)

SetEnabled flips a plugin on or off, loading or unloading it.

func (*Registry) SetHTTPTransport

func (r *Registry) SetHTTPTransport(t http.RoundTripper)

SetHTTPTransport replaces the transport http_request uses (tests).

func (*Registry) SetHomeResolver

func (r *Registry) SetHomeResolver(f func(ctx context.Context, u *model.User) string)

SetHomeResolver wires the answer to "where do this person's results go when not beside their source" (CallContext.Home) — the HTTP layer, which knows storages, tenants and the ACL.

func (*Registry) SetJobQueue

func (r *Registry) SetJobQueue(q JobQueue)

SetJobQueue wires the queue scheduled work is handed to.

func (*Registry) SetMailer

func (r *Registry) SetMailer(m mailSink)

SetMailer wires the mailer (nil → mail_send unavailable).

func (*Registry) SetNotify

func (r *Registry) SetNotify(n notifySink)

SetNotify wires the notification service (nil → notify_send unavailable).

func (*Registry) SetOrigins

func (r *Registry) SetOrigins(fn func(ctx context.Context, storageID int64) string)

SetOrigins records how an absolute link's origin is resolved when NO request is in scope — a plugin job runs on the queue, long after the browser left. The tenant then comes from the data (the share's storage), never from a guess: internal/tenanturl.Resolver.ForStorage is what is wired here. Without it a customer's signature request would carry the OPERATOR's hostname.

func (*Registry) SetOutputSink

func (r *Registry) SetOutputSink(s OutputSink)

SetOutputSink wires the committer that lands job outputs on a storage.

func (*Registry) SetPublicURL

func (r *Registry) SetPublicURL(base string)

SetPublicURL records the base the visitor link is built on. It is the fallback; SetOrigins is what a multi-tenant install answers with.

func (*Registry) SetStorageResolver

func (r *Registry) SetStorageResolver(fn func(int64) (storage.Driver, error))

SetStorageResolver wires (or replaces) the driver resolver jobs read through; the server builds it after the registry.

func (*Registry) SetUIOrigin added in v0.48.0

func (r *Registry) SetUIOrigin(origin string)

SetUIOrigin sets the separate origin interfaces are served from (FILEX_APP_UI_ORIGIN, with its base path, no trailing slash); "" for filex's own.

func (*Registry) SetUpgradeListener added in v0.48.0

func (r *Registry) SetUpgradeListener(f UpgradeListener)

SetUpgradeListener wires the listener (the realtime hub).

func (*Registry) SetUserScope

func (r *Registry) SetUserScope(fn func(ctx context.Context, u *model.User) context.Context)

SetUserScope wires the function that puts the acting user's tenant scope on a job context, so users_lookup from a queued job sees the same directory the user's own requests see. Nil = single-tenant, unscoped.

func (*Registry) SetVisibility

func (r *Registry) SetVisibility(fn func(ctx context.Context, u *model.User, storageID int64, rel string) bool)

SetVisibility wires the check state_list runs on every row: a plugin may keep state on a file the person asking has no right to see, and a listing is not a way around the ACL. Nil = everything passes.

func (*Registry) Settings

func (r *Registry) Settings(ctx context.Context, id int64) (map[string]string, []wire.Field, error)

Settings returns the stored values with secret ones masked as "***".

func (*Registry) StartScheduler

func (r *Registry) StartScheduler(ctx context.Context)

StartScheduler runs the schedule until ctx ends. Call it once at boot, after the job queue is wired.

It does not poll on a fixed beat: after each pass it sleeps until the next row is actually due, capped at schedulePollCeiling so a row another process wrote is picked up too. That is what makes "to the minute" true — an item due at 03:00:00 runs at 03:00:00, not at the next tick of a one-minute timer.

func (*Registry) StartSweeper

func (r *Registry) StartSweeper(ctx context.Context)

StartSweeper runs SweepPages every hour until ctx ends.

func (*Registry) StartUpdater added in v0.47.0

func (r *Registry) StartUpdater(ctx context.Context, enabled bool)

StartUpdater runs the daily check until ctx ends. enabled is the operator's switch (FILEX_APP_PLUGIN_UPDATE_CHECK); a demo never runs it. The time of the last check is stored, so a restart neither skips a day nor checks at every boot.

func (*Registry) StatusOf

func (r *Registry) StatusOf(p *Installed) *Status

StatusOf builds the list row. ⚠ Nil-safe receiver: the wire-fixture test calls it on a nil registry, which then reports languages without coverage.

func (*Registry) StorageName

func (r *Registry) StorageName(ctx context.Context, id int64) string

StorageName is a storage's name, or "" when it is gone (or unreadable) — what the panel prints beside a lock instead of the storage's id.

func (*Registry) SweepPages

func (r *Registry) SweepPages(ctx context.Context) int

SweepPages removes the exposed copies of links that are gone. Called hourly by the server; safe to call any time.

⚠ It walks the DIRECTORIES rather than querying for dead rows, because a directory can outlive its row: DeleteShare (the administrator's hard delete) leaves nothing to join against, and those copies would then sit on disk until somebody noticed. A directory is removed when its share is gone, or when the share has been dead for longer than the grace period.

func (*Registry) SweepSchedule

func (r *Registry) SweepSchedule(ctx context.Context) int64

SweepSchedule drops finished rows whose due time is past the retention window. Called once an hour from the schedule loop.

It measures on due_at rather than updated_at on purpose: due_at is always written from Go and updated_at from the engine's CURRENT_TIMESTAMP, and comparing a Go time against an engine-formatted column is how a sweep silently stops matching anything.

func (*Registry) Tick

func (r *Registry) Tick(ctx context.Context, p *Installed, now, windowEnd time.Time) (*wire.TickOutput, error)

Tick wakes one plugin and returns what it wants done. Exported so a test (and a future "run it now" button) can drive one wake-up on its own.

The call gets a READ-ONLY scope with no storage and no actor: the same shape a screen gets. It may read settings and its own state, look people up, notify, mail and make granted HTTP calls; it may not write files, write state, take locks or sign. Those belong in the actions it schedules, which run as real jobs with a real scope.

func (*Registry) UICall added in v0.48.0

func (r *Registry) UICall(ctx context.Context, plugin, view string, storageID int64, rels []string, actor *model.User, locale, method string, params json.RawMessage) (json.RawMessage, error)

UICall runs `ui_call` for view (an interface view of plugin) with method and params, on the files the interface was opened with.

func (*Registry) UIHandler added in v0.48.0

func (r *Registry) UIHandler(origin func(*http.Request) (string, bool)) http.Handler

UIHandler answers the interface route. `origin` says, per request, the origin (with the base path, no trailing slash) the browser reached this route at — `<P>` is built from it — and whether the request may be served here at all (false: 404, e.g. a separate interface origin is configured and this is not it).

func (*Registry) UILocale

func (r *Registry) UILocale(code string) map[string]string

UILocale is one language's strings, merged over every running app that ships it; nil when none does.

⚠ First app (by name) wins a contested key — arbitrary, but stable across restarts, which is what matters to somebody reading the screen. ⚠ An empty value is left out rather than served: an app that has not translated a string yet must fall back to English, not blank it.

func (*Registry) UILocaleCodes

func (r *Registry) UILocaleCodes() []string

UILocaleCodes lists the languages the running apps add, sorted — the tags the server's catalogue may answer in besides English and Turkish.

func (*Registry) UILocaleList

func (r *Registry) UILocaleList() []UILocaleInfo

UILocaleList is every language the running apps add, by tag.

func (*Registry) UIString

func (r *Registry) UIString(code, key string) (string, bool)

UIString is ONE key of one language, under the same rules as UILocale (running apps only, first app by name wins, an empty value is no value) — without building the whole ~3 000-key map. It is how the server's own catalogue (internal/srvtext) reads a pack: a mail or a public page asks for a few dozen keys, and UILocale would merge every string of every pack for each of them.

func (*Registry) Unlock

func (r *Registry) Unlock(ctx context.Context, storageID int64, rel string) (bool, error)

Unlock lifts a lock by force — the administrator's override for a plugin that will never come back to lift it. Returns false when nothing was locked there.

func (*Registry) Upgrade

func (r *Registry) Upgrade(ctx context.Context, id int64, in *InstallInput) (*Status, *DryRunAnswer, error)

Upgrade replaces the module + manifest of an installed plugin. A manifest that asks for a permission not yet granted answers permissions_changed until the request grants it; a narrower manifest simply narrows the grant.

func (*Registry) VerifyUnlock

func (r *Registry) VerifyUnlock(token, value string) bool

VerifyUnlock reports whether an unlock cookie belongs to this link.

func (*Registry) ViewEvent

func (r *Registry) ViewEvent(ctx context.Context, plugin, view string, storageID int64, rels []string, actor *model.User, locale string, in wire.ViewEventInput) (*wire.Surface, error)

ViewEvent runs one view event and returns the sanitised surface.

func (*Registry) WakeSoon

func (r *Registry) WakeSoon(ctx context.Context, pluginID int64, why string)

WakeSoon brings an app's hourly wake-up forward to a few seconds from now.

It is how the host tells an app that something it cannot see happened to one of its own things — today, a PERSON ended one of its links from the Shares screen (handlers: appLinkEnded). ⚠⚠ It carries no event and no payload, on purpose: the app is not told WHAT happened, it is woken, and its `tick` asks the host about its own links (`share_state` → the link's facts) exactly as it does every hour. One mechanism — the wake-up — and one way of learning — asking — rather than a revoke callback living beside them with its own delivery, retry and ordering rules. Without the nudge the same wake-up still notices within the hour; the nudge only makes it prompt.

A no-op for an app without the `schedule` grant, a stopped app and a demo instance — each of those is simply not woken, which is what they already were.

type Runtime

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

Runtime owns the compilation cache and the per-host settings.

func NewRuntime

func NewRuntime(cacheDir string) (*Runtime, error)

New prepares a runtime. cacheDir holds compiled modules; "" disables the on-disk cache (tests).

func (*Runtime) Close

func (r *Runtime) Close(ctx context.Context) error

Close releases the compilation cache.

func (*Runtime) Compile

func (r *Runtime) Compile(ctx context.Context, wasmPath, sha256Hex string, m *Manifest, fns []HostFunc, logger func(level, msg string)) (*Compiled, error)

Compile validates the module against sha256 (lower-hex; "" skips the check) and compiles it with the given host functions.

type Scope

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

Scope is one call's ticket. Build with newScope; close with Close.

func (*Scope) AddInput

func (s *Scope) AddInput(rel string, size int64, mime string) wire.FileRef

AddInput registers a storage file the call may read.

func (*Scope) Close

func (s *Scope) Close()

Close shuts every open handle and removes the spool.

⚠ It also abandons any share this call PROMISED and did not keep (a job that failed, an output the plugin never named). Nothing has to be undone in the database — a promise is the absence of a row — but the copies staged for it are removed here, on every exit path, rather than left for the sweeper.

func (*Scope) Inputs

func (s *Scope) Inputs() []wire.FileRef

Inputs lists the registered inputs in order.

func (*Scope) Output

func (s *Scope) Output(ref string) (*scopeFile, bool)

Output returns a plugin-created file by ref, for committing.

type Status

type Status struct {
	ID          int64     `json:"id"`
	Name        string    `json:"name"`
	Version     string    `json:"version"`
	Label       wire.Text `json:"label"`
	Description wire.Text `json:"description,omitempty"`
	Icon        string    `json:"icon,omitempty"`
	Homepage    string    `json:"homepage,omitempty"`
	Enabled     bool      `json:"enabled"`
	State       string    `json:"state"`
	StateError  string    `json:"state_error,omitempty"`
	Source      string    `json:"source"`
	SourceURL   string    `json:"source_url,omitempty"`
	// ManifestURL: where a URL install reads its manifest (the update check's
	// address; SourceURL is the module's for an app with one).
	ManifestURL string   `json:"manifest_url,omitempty"`
	SHA256      string   `json:"sha256"`
	Signed      bool     `json:"signed"`
	Permissions []string `json:"permissions"`
	// Scheduled says this app is woken once an hour and runs work of its
	// own choosing — the one thing on this row that happens with nobody
	// present, so the list can mark it.
	Scheduled   bool `json:"scheduled"`
	Actions     int  `json:"actions"`
	Views       int  `json:"views"`
	PublicPages int  `json:"public_pages"`
	// Kind is KindApp or KindLanguagePack (langpack.go). A language pack has
	// no module, so `sha256` is the hash of its MANIFEST — the thing that was
	// verified and, on an instance that requires signatures, signed.
	Kind string `json:"kind"`
	// Languages are the languages this app adds to filex itself, each with
	// how much of the CURRENT catalogue it covers. Absent when it adds none.
	Languages []LanguageRow `json:"languages,omitempty"`
	// Compat is the manifest's `filex` range against the running filex
	// (compat.go); absent when it names none. `ok: false` on an installed app
	// is a warning, not a stop: it keeps running (Load says why).
	Compat *Compat `json:"compat,omitempty"`
	// UpdateSource is where newer versions are looked for: github | url, or
	// absent when there is nowhere to ask (an uploaded app).
	UpdateSource string `json:"update_source,omitempty"`
	// Update is what the last check found; absent before the first one.
	Update *UpdateInfo `json:"update,omitempty"`
	// Engine: the app runs a WebAssembly module. False for a language pack
	// and for an app that is only an interface.
	Engine bool `json:"engine"`
	// UI describes the app's own interface; absent when it has none.
	UI *UIInfo `json:"ui,omitempty"`
	// Previous is the version an upgrade replaced, kept to go back to
	// (versions.go); absent when none is kept.
	Previous  *PreviousVersion `json:"previous,omitempty"`
	CreatedAt time.Time        `json:"created_at"`
	UpdatedAt time.Time        `json:"updated_at"`
}

Status is one plugin as the admin list shows it.

type UIExternalRow added in v0.48.0

type UIExternalRow struct {
	URL string `json:"url"`
	As  string `json:"as"`
	// Mode is "mirror" (filex serves its own checked copy; no browser asks
	// the address) or "live" (the reader's browser fetches it).
	Mode   string    `json:"mode"`
	SHA256 string    `json:"sha256,omitempty"`
	Bytes  int64     `json:"bytes,omitempty"`
	Reason wire.Text `json:"reason,omitempty"`
	// Path is where a mirrored file is served inside the package.
	Path string `json:"path,omitempty"`
}

UIExternalRow is one external address as the review shows it.

type UIFileDiff added in v0.48.0

type UIFileDiff struct {
	Added        []string `json:"added,omitempty"`
	Removed      []string `json:"removed,omitempty"`
	Changed      []string `json:"changed,omitempty"`
	AddedCount   int      `json:"added_count"`
	RemovedCount int      `json:"removed_count"`
	ChangedCount int      `json:"changed_count"`
}

UIFileDiff is how an upgrade's interface files differ, by name. Each list is cut at maxUIFileDiff names; the counts are whole.

type UIInfo added in v0.48.0

type UIInfo struct {
	SHA256 string `json:"sha256"`
	// Files and Bytes are the bundle's file count and zipped size.
	Files int   `json:"files"`
	Bytes int64 `json:"bytes"`
	// Unpacked is what the files come to.
	Unpacked int64 `json:"unpacked"`
	// CSP are the script-policy exceptions it asks for.
	CSP []string `json:"csp,omitempty"`
	// External are the addresses outside the package, each mirrored or live.
	External []UIExternalRow `json:"external,omitempty"`
}

UIInfo is an interface as the admin screens describe it — the install review, the upgrade's comparison, the Apps list.

type UILocaleInfo

type UILocaleInfo struct {
	Code string `json:"code"`
	// Source is always "plugin" here; the field is the client's
	// PublicLocaleOption, which also describes the built-in languages.
	Source string `json:"source"`
	// Plugin names the app that brought it (the first, by name, when several
	// do) — shown beside the language so nobody wonders where it came from.
	Plugin string `json:"plugin"`
	RTL    bool   `json:"rtl,omitempty"`
}

UILocaleInfo is one language the running apps add, as GET /api/public/branding lists it. ⚠ No strings: those are fetched for the one language a visitor actually picks (UILocale, GET /api/public/ui-locales/ {code}). A complete language is ~150 KB of JSON, and the branding answer is read by every public page and every panel start.

type UIRef added in v0.48.0

type UIRef struct {
	// URL is the interface file's address: relative to the server's root
	// (`/_appui/<app>/<sha16>/<file>`, joined with the client's API base),
	// or absolute when interfaces have an origin of their own.
	URL string `json:"url"`
	// Grants are what the app was granted — what its bridge calls may do.
	Grants []string `json:"grants"`
	// Engine: the app has a module (engine.call and job.submit answer).
	Engine bool `json:"engine"`
	// Version is the app's version (the "updated" note compares it).
	Version string `json:"version"`
}

UIRef is how a client opens an app's own interface.

type URLInput

type URLInput struct {
	URL         string `json:"url"`
	ManifestURL string `json:"manifest_url"`
	SHA256      string `json:"sha256"`
	Signature   string `json:"signature"`
}

URLInput is the JSON body of a URL install.

type UpdateInfo added in v0.47.0

type UpdateInfo struct {
	CheckedAt *time.Time `json:"checked_at,omitempty"`
	Status    string     `json:"status,omitempty"`
	// Version is the newer version found (available, needs_approval,
	// failed), or the newest one this filex cannot run (incompatible).
	Version string `json:"version,omitempty"`
	// Ref is the git ref that version is read from (a GitHub source).
	Ref string `json:"ref,omitempty"`
	// Requires: incompatible — the range that version declares.
	Requires string `json:"requires,omitempty"`
	// Notes are the source's release notes for Version, as plain text
	// (a GitHub release's body, clipped).
	Notes string `json:"notes,omitempty"`
	// Added / AddsModule: needs_approval — what the administrator is asked
	// to approve (DryRunUpgrade).
	Added      []string `json:"added,omitempty"`
	AddsModule bool     `json:"adds_module,omitempty"`
	// Refusal: failed / check_failed — the same refusal an install answers,
	// so the panel says it in the reader's words with the wizard's sentences.
	Refusal *InstallRefusal `json:"refusal,omitempty"`
	// Auto is the last automatic update filex 0.47 made, kept across checks
	// for the record. Nothing sets it any more.
	Auto *AutoUpdated `json:"auto,omitempty"`
	// Announced is "<status>@<version>" as last told to the administrators,
	// so a daily check does not ring the bell again for the same thing.
	Announced string `json:"announced,omitempty"`
}

UpdateInfo is what the last check found for one app — stored with it (app_plugins.update_json) and shown on the Apps list.

type UpdateReport added in v0.47.0

type UpdateReport struct {
	CheckedAt     time.Time `json:"checked_at"`
	Checked       int       `json:"checked"`
	Updated       []string  `json:"updated"`
	Available     []string  `json:"available"`
	NeedsApproval []string  `json:"needs_approval"`
	Failed        []string  `json:"failed"`
}

UpdateReport is one check, as "Check now" answers it: how many apps were asked, and the names of those that moved, wait, or failed.

type UpgradeListener added in v0.48.0

type UpgradeListener func(app, version string)

UpgradeListener hears every approved version change (an upgrade, a roll-back): the explorers that are open tell their interfaces to reload.

type UserRow

type UserRow struct {
	// UserID, not `id`: the row doubles as a people-picker VALUE, whose
	// shape the surface contract fixes as {user_id, email, name}.
	UserID int64  `json:"user_id"`
	Email  string `json:"email"`
	Name   string `json:"name,omitempty"`
}

UserRow is what users_lookup and GET /api/files/plugins/users return: the directory entry a picker needs and nothing else (no role, no flags).

type ViewRow

type ViewRow struct {
	Plugin    string       `json:"plugin"`
	ID        string       `json:"id"`
	Placement string       `json:"placement"`
	Label     wire.Text    `json:"label"`
	Icon      string       `json:"icon,omitempty"`
	Applies   wire.Applies `json:"applies"`
	// UI: the view is the app's own interface.
	UI *UIRef `json:"ui,omitempty"`
}

ViewRow is one non-modal view (inspector/home) the explorer may offer.

Jump to

Keyboard shortcuts

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