Documentation
¶
Overview ¶
Package api serves the HTTP interface used by the web front end.
The important design constraint: channels are read from and written to files on disk. The API is an editor for those files, not a different storage system that happens to export YAML. A channel created in the browser and one written by hand are the same artifact, which is what makes "export to share" trivial and keeps git as the history of record.
Index ¶
- Variables
- func EnsureDemoAccount(ctx context.Context, st *store.Store, d *DemoAccount) error
- func FigureTiles() []string
- type CDSClient
- type CMS0057Status
- type ChannelRepo
- func (r *ChannelRepo) Create(raw []byte) (*config.Channel, error)
- func (r *ChannelRepo) Delete(name string) error
- func (r *ChannelRepo) Get(name string) (*config.Channel, error)
- func (r *ChannelRepo) List() (valid []ChannelSummary, broken map[string]string, err error)
- func (r *ChannelRepo) PathFor(name string) (string, error)
- func (r *ChannelRepo) RawYAML(name string) ([]byte, error)
- func (r *ChannelRepo) ReadTables(file string) (*codeset.Set, error)
- func (r *ChannelRepo) Save(cfg *config.Channel) error
- func (r *ChannelRepo) TableFiles() []string
- func (r *ChannelRepo) Update(name string, raw []byte) (*config.Channel, error)
- func (r *ChannelRepo) Validate(raw []byte) (*config.Channel, error)
- func (r *ChannelRepo) WriteLibrary(file string, source []byte) (string, error)
- func (r *ChannelRepo) WriteSidecar(channel, file string, body []byte) error
- func (r *ChannelRepo) WriteTables(file string, body []byte) error
- type ChannelState
- type ChannelSummary
- type ClusterControl
- type ComponentView
- type DemoAccount
- type DestinationState
- type DirectContact
- type Drugs
- type FieldView
- type Figure
- type FigureLink
- type FigureRow
- type FrictionFunnel
- type FrictionReport
- type LDAPConfig
- type MCPSettings
- type MeasureService
- type MessageView
- type OIDCConfig
- type Runtime
- func (rt *Runtime) Channel(name string) (*engine.Channel, bool)
- func (rt *Runtime) ChannelStates() (configured []string, running map[string]bool)
- func (rt *Runtime) CheckContractsDue(ctx context.Context, channels []*config.Channel)
- func (rt *Runtime) ContractDrift() map[string]alerts.ContractDrift
- func (rt *Runtime) EnableContracts(log *slog.Logger)
- func (rt *Runtime) IsRunning(name string) bool
- func (rt *Runtime) RestartUsingTables(ctx context.Context, file string, log *slog.Logger) (restarted []string, failed map[string]string)
- func (rt *Runtime) Route(ctx context.Context, name string, raw []byte) error
- func (rt *Runtime) RunningChannels() []*engine.Channel
- func (rt *Runtime) Start(name string, log *slog.Logger) error
- func (rt *Runtime) StartEnabled(log *slog.Logger) []error
- func (rt *Runtime) States() []ChannelState
- func (rt *Runtime) Stop(ctx context.Context, name string) error
- func (rt *Runtime) StopAll(ctx context.Context)
- func (rt *Runtime) TenantName() string
- type SAMLConfig
- type SegmentView
- type Server
- func (s *Server) BeginDraining()
- func (s *Server) Draining() bool
- func (s *Server) Handler() http.Handler
- func (s *Server) MarkReady()
- func (s *Server) OutboundClient() *http.Client
- func (s *Server) Routes() []routeRecord
- func (s *Server) ViewLibrary() func(ctx context.Context, reference, canonical string) ([]byte, bool)
- type SettingsFiles
- type StartupSettings
- type TenantRepos
- type TenantRuntimes
- type Trend
- type ValidationFailure
- type ViewRunner
Constants ¶
This section is empty.
Variables ¶
var ( // ErrChannelNotFound means no channel of that name exists. ErrChannelNotFound = errors.New("channel not found") // ErrChannelExists means a channel of that name already exists. ErrChannelExists = errors.New("a channel with that name already exists") // ErrInvalidName means the name cannot be used as a filename. ErrInvalidName = errors.New("invalid channel name") )
Repository errors.
var ErrBadLibraryPath = errors.New("a script library is written as lib/<name>.js or lib/<name>.lua, beside the channels")
ErrBadLibraryPath means a library file was named outside lib/ or without a .js or .lua extension.
Functions ¶
func EnsureDemoAccount ¶ added in v0.1.12
EnsureDemoAccount makes the account exist as a viewer with the published password, and puts it back that way at every start: if anyone with more rights changed it, the page would otherwise go on showing a password that no longer works.
func FigureTiles ¶ added in v0.1.7
func FigureTiles() []string
FigureTiles are the tiles this endpoint draws.
Types ¶
type CDSClient ¶ added in v0.1.5
type CDSClient struct {
// Issuer is the iss the EHR signs with.
Issuer string `yaml:"issuer" json:"issuer"`
// JWKSURL is where its public keys are published.
JWKSURL string `yaml:"jwks_url" json:"jwksUrl"`
// DaVinciVersion is the CRD release the EHR speaks, "2.1" or "2.2", when its requests do not say (requestedVersion);
// empty leaves it to the server's -davinci-version.
DaVinciVersion string `yaml:"davinci_version,omitempty" json:"davinciVersion,omitempty"`
// contains filtered or unexported fields
}
CDSClient is an EHR allowed to call the CRD service with a signed JWT.
func LoadCDSClients ¶ added in v0.1.5
LoadCDSClients reads the trusted EHR list.
type CMS0057Status ¶ added in v0.1.5
type CMS0057Status struct {
FHIR bool `json:"fhir"`
BaseURL string `json:"baseUrl"`
SMART bool `json:"smart"`
BulkExport bool `json:"bulkExport"`
PayerAPIs bool `json:"payerApis"`
Consent bool `json:"consentRequired"`
// Subscriptions is topic-based FHIR subscriptions (encounter and appointment notifications).
Subscriptions bool `json:"subscriptions"`
ReadOnly bool `json:"readOnly"`
}
CMS0057Status is what the serve command knows about the FHIR endpoint, which decides whether each API can answer.
type ChannelRepo ¶
type ChannelRepo struct {
// Dir is where channel files live.
Dir string
// contains filtered or unexported fields
}
ChannelRepo reads and writes channel definitions as files.
This is the whole point of the design: the browser edits files. A channel built by clicking and a channel written by hand are the same artifact, so git holds the history, a diff shows what changed, and exporting to share is just handing someone the file.
func NewChannelRepo ¶
func NewChannelRepo(dir string) (*ChannelRepo, error)
NewChannelRepo prepares a repository, creating the directory if needed.
func (*ChannelRepo) Create ¶
func (r *ChannelRepo) Create(raw []byte) (*config.Channel, error)
Create writes a new channel. It refuses to overwrite an existing one.
func (*ChannelRepo) Delete ¶
func (r *ChannelRepo) Delete(name string) error
Delete removes a channel file.
func (*ChannelRepo) Get ¶
func (r *ChannelRepo) Get(name string) (*config.Channel, error)
Get returns one channel definition as the generic map the front end edits, along with the raw YAML.
func (*ChannelRepo) List ¶
func (r *ChannelRepo) List() (valid []ChannelSummary, broken map[string]string, err error)
List returns every channel, valid or not.
An invalid file is reported rather than hidden: a channel that will not load is exactly what an operator needs to see, and omitting it would make a broken file look like a missing one.
func (*ChannelRepo) PathFor ¶
func (r *ChannelRepo) PathFor(name string) (string, error)
pathFor finds the file defining a channel, matching on the name inside the file rather than assuming the filename. PathFor returns the file backing a channel. Exported for the history handlers, which need the filename rather than the parsed channel.
func (*ChannelRepo) RawYAML ¶
func (r *ChannelRepo) RawYAML(name string) ([]byte, error)
RawYAML returns the file contents, for the export button and the advanced editor.
func (*ChannelRepo) ReadTables ¶
func (r *ChannelRepo) ReadTables(file string) (*codeset.Set, error)
ReadTables loads a shared mapping table file, returning an empty set when it does not exist yet.
Absent is not an error here: the first table written to a server creates the file, and treating "no file" as a failure would make creating the first one impossible through the same path that creates the second.
func (*ChannelRepo) Save ¶
func (r *ChannelRepo) Save(cfg *config.Channel) error
Save writes a channel definition back, keeping its existing file.
Distinct from Update, which takes raw YAML from a person. This takes a definition the server has changed itself - attaching a contract that was just written, for instance - and re-serialises it.
func (*ChannelRepo) TableFiles ¶
func (r *ChannelRepo) TableFiles() []string
TableFiles lists the shared mapping table files in the channels directory.
Named by convention rather than by being referenced, so a table nobody uses yet is still found. Without this the only way to discover a table was through a channel that loaded it, which made a newly created one invisible in the section that had just created it.
func (*ChannelRepo) Update ¶
Update replaces an existing channel.
Renaming is allowed: the file is written under the new name and the old one removed, so the filename keeps matching the channel name.
func (*ChannelRepo) Validate ¶
func (r *ChannelRepo) Validate(raw []byte) (*config.Channel, error)
Validate parses and checks a definition without saving it, so the front end can tell someone what is wrong before they commit to it.
func (*ChannelRepo) WriteLibrary ¶ added in v0.1.5
func (r *ChannelRepo) WriteLibrary(file string, source []byte) (string, error)
WriteLibrary writes a shared script file that channels include, such as a Mirth code template library carried across.
Only lib/<name>.js or .lua: the caller is an editor, and a path they choose must not reach a channel file, the database or anything outside this directory. Overwriting is allowed, because importing the same backup twice should converge rather than fail.
func (*ChannelRepo) WriteSidecar ¶
func (r *ChannelRepo) WriteSidecar(channel, file string, body []byte) error
WriteSidecar writes a companion file beside a channel definition.
Contracts, code sets and the like live next to the channel that references them, because a channel and the things it depends on travel together: copying one file to another server and leaving its contract behind produces a channel that will not load at all, which is a confusing way to learn about a missing file.
The name is deliberately restricted rather than trusted. It arrives from a request, and a path with a separator or a parent reference in it would write outside the channels directory - which for a process that may be running as a service is a way to overwrite something that matters. Only a plain file name is allowed.
func (*ChannelRepo) WriteTables ¶
func (r *ChannelRepo) WriteTables(file string, body []byte) error
WriteTables replaces a shared mapping table file.
type ChannelState ¶
type ChannelState struct {
Name string `json:"name"`
Running bool `json:"running"`
StartedAt *time.Time `json:"startedAt,omitempty"`
Error string `json:"error,omitempty"`
Received int64 `json:"received"`
Delivered int64 `json:"delivered"`
Filtered int64 `json:"filtered"`
Partial int64 `json:"partial"`
Failed int64 `json:"failed"`
Unparseable int64 `json:"unparseable"`
Destinations []DestinationState `json:"destinations,omitempty"`
}
ChannelState is the live state of one channel.
type ChannelSummary ¶
type ChannelSummary struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
// Group is what this channel is filed under. Empty means ungrouped, which is a legitimate answer
// and not a missing value - a site with nine channels should not be made to invent a taxonomy.
Group string `json:"group,omitempty"`
Enabled bool `json:"enabled"`
Listen string `json:"listen"`
SourceType string `json:"sourceType"`
AckWhen string `json:"ackWhen"`
Filter string `json:"filter,omitempty"`
Destinations []string `json:"destinations"`
Reads []string `json:"reads,omitempty"`
File string `json:"file"`
}
ChannelSummary is the listing view.
type ClusterControl ¶ added in v0.1.17
type ClusterControl interface {
Status(ctx context.Context) (cluster.Status, error)
Runs(tenant, channel string) bool
SetDesired(ctx context.Context, tenant, channel string, running bool, by string) error
StepDown(ctx context.Context, d time.Duration, by string) error
Move(ctx context.Context, tenant, channel, toNode, by string) error
IsLeader() bool
// RunningOn names the server running a channel, "" when none does.
RunningOn(ctx context.Context, tenant, channel string) string
}
ClusterControl is what the console and API need from a server in a cluster: its view of the cluster, where a channel runs, and the controls (start and stop anywhere, step down, move).
type ComponentView ¶
type ComponentView struct {
Number int `json:"number"`
Path string `json:"path"`
Name string `json:"name,omitempty"`
Value string `json:"value"`
}
ComponentView is one component of a composite field.
type DemoAccount ¶ added in v0.1.12
DemoAccount is a shared, published sign-in for a public demonstration server.
Its password is shown on the sign-in page, so it is a viewer (it can look at everything and change nothing), and it can never change its own password or add a passkey: the first visitor to do either would lock out everyone after them.
func ParseDemoAccount ¶ added in v0.1.12
func ParseDemoAccount(v string) (*DemoAccount, error)
ParseDemoAccount reads "username:password".
type DestinationState ¶
type DestinationState struct {
Name string `json:"name"`
Delivered int64 `json:"delivered"`
Failed int64 `json:"failed"`
Filtered int64 `json:"filtered"`
}
DestinationState is per-destination counts for a running channel.
type DirectContact ¶ added in v0.1.16
type DirectContact struct {
// Address is a Direct address, or a domain for a domain-bound certificate that covers every address at it.
Address string `json:"address"`
Name string `json:"name,omitempty"`
Organisation string `json:"organisation,omitempty"`
Notes string `json:"notes,omitempty"`
// Source says where the certificate came from: dns, upload, or none yet.
Source string `json:"source"`
Added time.Time `json:"added"`
Updated time.Time `json:"updated"`
Certs []directCertShown `json:"certificates"`
}
DirectContact is one correspondent.
type Drugs ¶ added in v0.1.16
type Drugs struct {
Formulary *formulary.File
// FormularyBase is the public Da Vinci US Drug Formulary endpoint's address.
FormularyBase string
RTPB rtpb.Options
// EPA answers SCRIPT ePA; its Submit is nil when PAS is off, which leaves initiation working and PARequests refused.
EPA *epa.Service
// ReplyChannel names the channel unsolicited ePA responses are routed to, or "".
ReplyChannel string
}
Drugs is what the drug screens and endpoints need. Nil Formulary leaves them off.
type FieldView ¶
type FieldView struct {
Number int `json:"number"`
Path string `json:"path"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Value string `json:"value"`
Raw string `json:"raw"`
Empty bool `json:"empty"`
Repeats int `json:"repeats,omitempty"`
Meaning string `json:"meaning,omitempty"`
Components []ComponentView `json:"components,omitempty"`
}
FieldView is one field, explained.
type Figure ¶ added in v0.1.7
type Figure struct {
Rows []FigureRow `json:"rows"`
// Note says what the figure covers (the window, the source); Unavailable, when set, is why it could not be computed.
Note string `json:"note,omitempty"`
Empty string `json:"empty,omitempty"`
// Links are downloads that go with the figure, such as each partner's connection sheet.
Links []FigureLink `json:"links,omitempty"`
}
Figure is one tile's content.
type FigureLink ¶ added in v0.1.7
FigureLink is a download beside a figure.
type FigureRow ¶ added in v0.1.7
type FigureRow struct {
Label string `json:"label"`
Value string `json:"value"`
Tone string `json:"tone,omitempty"`
}
FigureRow is one line of a figure: a label, a value, and how to colour it (ok, warn, bad, or empty for neutral).
type FrictionFunnel ¶
type FrictionFunnel struct {
FirstSignIn *time.Time `json:"firstSignIn,omitempty"`
FirstChannel *time.Time `json:"firstChannel,omitempty"`
FirstMessage *time.Time `json:"firstMessage,omitempty"`
ChannelCount int `json:"channelCount"`
MessageCount int64 `json:"messageCount"`
// Delivered is how many messages reached a destination. The number that separates a channel somebody configured from one that
// does the job: a channel can receive for weeks and deliver nothing, and from inside the interface that looks like traffic.
Delivered int64 `json:"delivered"`
// StuckAt names the first step that has not happened, rather than leaving a reader to work it out from four timestamps. The whole
// value of the funnel is in saying where somebody stopped, and a report that needs interpreting gets interpreted by whoever wrote
// the software.
StuckAt string `json:"stuckAt"`
// MinutesToFirstMessage is from being able to sign in to a message actually arriving. The single most useful number here, and the
// one a person watching over somebody's shoulder would have been timing.
MinutesToFirstMessage *float64 `json:"minutesToFirstMessage,omitempty"`
}
FrictionFunnel is the sequence a new installation has to get through before Perfuse does anything useful.
Each step is a fact looked up elsewhere: the audit trail for signing in and creating a channel, the message store for traffic. If a step has not happened, that is the answer - an installation stuck at "a channel exists but no message has ever arrived" is the most interesting state this report can show, and it is invisible from inside.
type FrictionReport ¶
type FrictionReport struct {
// Refusals are the messages hit most often, worst first. The point of the whole report.
Refusals []store.FrictionGroup `json:"refusals"`
// Total is every refusal recorded, so the list above can be read as a sample of something rather than as everything.
Total int `json:"total"`
// Funnel is how far the installation has got. Derived from records that already existed rather than newly written, because a
// milestone this code records about itself is worth less than one it has to go and find.
Funnel FrictionFunnel `json:"funnel"`
}
FrictionReport is what the report endpoint returns.
type LDAPConfig ¶
type LDAPConfig struct {
// Directory talks to the directory.
Directory *ldap.Directory
// Settings are the loaded configuration, for role mapping and the button label.
Settings *ldap.Config
}
LDAPConfig holds the directory the server authenticates against.
type MCPSettings ¶ added in v0.1.16
type MCPSettings struct {
// Enabled is the switch; without it /mcp answers 404.
Enabled func() bool
// CallsPerMinute bounds tool calls per token; 0 is 60.
CallsPerMinute func() int
// Origins are browser origins allowed to call /mcp, beside this server's own. Requests with no Origin (agents, CLIs)
// are allowed; a browser page on another origin is refused, which is what MCP asks of a server for DNS rebinding.
Origins []string
}
MCPSettings switch MCP on and bound it.
type MeasureService ¶ added in v0.1.16
type MeasureService interface {
ListMeasures(ctx context.Context) ([]fhirserver.MeasureSummary, error)
MeasureJSON(ctx context.Context, id string) ([]byte, error)
PutMeasure(ctx context.Context, raw []byte) (string, error)
DeleteMeasure(ctx context.Context, id string) error
EvaluateMeasure(ctx context.Context, req fhirserver.EvaluateMeasureRequest, caller *fhirserver.Caller) (map[string]any, error)
}
MeasureService is the FHIR server's measure side (fhirserver.Server); nil without -fhir.
type MessageView ¶
type MessageView struct {
MessageType string `json:"messageType"`
TriggerEvent string `json:"triggerEvent"`
EventMeaning string `json:"eventMeaning,omitempty"`
ControlID string `json:"controlId"`
Version string `json:"version"`
Sender string `json:"sender"`
Segments []SegmentView `json:"segments"`
Separators string `json:"separators"`
}
MessageView is a whole message, explained.
type OIDCConfig ¶
type OIDCConfig struct {
// Issuer is the identity provider's issuer URL.
Issuer string
// ClientID and ClientSecret identify Perfuse to the provider.
ClientID string
ClientSecret string
// RedirectURL is where the provider sends the browser back. Must match what is registered with the provider exactly.
RedirectURL string
// Scopes are requested at sign-in. Empty asks for openid, profile and email.
Scopes []string
// Roles maps the provider's groups to Perfuse roles.
Roles *oidc.RoleMapping
// CreateUsers allows a first-time sign-in to create an account. Defaults to false.
//
// False is the safe default and the useful one to explain: with it off, somebody has to exist in Perfuse before they
// can sign in, so the directory decides who they are and Perfuse decides who is allowed in. With it on, anybody the
// directory vouches for and whose groups map to a role gets an account - which is what most sites want, and is a
// decision they should make deliberately.
CreateUsers bool
// Label is what the sign-in button says. Defaults to naming the issuer's host.
Label string
// Provider is the discovered provider, filled in at startup.
Provider *oidc.Provider
// Keys are the provider's signing keys.
Keys *oidc.KeySet
}
OIDCConfig is how federated sign-in is configured.
func (*OIDCConfig) ButtonLabel ¶
func (c *OIDCConfig) ButtonLabel() string
ButtonLabel is what the sign-in button should say.
The issuer's host when nothing is configured, because "sign in with 127.0.0.1" is unhelpful but "sign in with SSO" is worse - a site with two providers would show the same word twice.
func (*OIDCConfig) Enabled ¶
func (c *OIDCConfig) Enabled() bool
Enabled reports whether federated sign-in is configured.
type Runtime ¶
type Runtime struct {
// Channels supplies channel definitions from disk.
Repo *ChannelRepo
// Messages persists what each channel handled. Optional.
Messages *msgstore.Store
// FHIR is the embedded FHIR store, if one is configured.
FHIR *fhirserver.Store
// Metrics collects what the engine is doing. Optional; when nil the metrics
// endpoints say so rather than returning empty series that look like an idle
// system.
Metrics *metrics.Collector
// Queues is the durable delivery queue, nil when queueing is unavailable.
Queues *engine.Queues
// Admit bounds deliveries in flight, shared with every channel this runtime starts.
//
// The same controller the engine uses, on purpose. A channel started from the interface must draw on the
// same descriptor budget as one started at boot, or the limit is per channel and therefore not a limit.
Admit *admit.Controller
// Terminology counts the values mapping tables did not have; nil when there is no database.
Terminology *terminology.Store
// QueryState records what a DICOM query source has already seen, nil when there is no database.
//
// A channel that polls an archive refuses to start without it rather than running stateless: with nothing to
// remember it re-emits every matching study on every poll, which against a real archive is a message storm that
// looks like the archive malfunctioning.
QueryState engine.DICOMQueryState
// PHIWatch watches the traffic of every channel this runtime starts for the patterns a breach looks like. Nil
// when it is off.
PHIWatch *compliance.Monitor
// Tracer exports message traces. Nil when tracing is not configured.
Tracer *trace.Tracer
// Contracts checks each channel's contract against recent traffic, on a timer.
//
// Nil when nothing has a contract. Created lazily by EnableContracts so a server with no contracts does
// no work and holds no state for them.
Contracts *contractChecker
// Tenant is which tenant this runtime serves. Empty in single-tenant operation,
// which reports as the default tenant in metrics rather than as an empty label.
Tenant tenant.ID
// Admission is asked before each received message on every channel of this runtime; nil admits everything. It is
// the tenant's limits (tenant.Gate), so one customer cannot crowd out the rest of a shared server.
Admission func() error
// Gate, on a server in a cluster, refuses to start a channel another server runs: the error says where.
Gate func(name string) error
// Expected, on a server in a cluster, says whether a channel not running here counts as down: one another server
// runs does not.
Expected func(name string) bool
// contains filtered or unexported fields
}
Runtime owns the running engine, so the interface can show what is happening and start or stop it.
Without this the web interface is a text editor with a login: you can change a channel but not see whether it is running, how many messages it has handled, or look at one that failed. That gap is the difference between a configuration tool and an operations console.
func NewRuntime ¶
func NewRuntime(repo *ChannelRepo, messages *msgstore.Store, fhirStore *fhirserver.Store) *Runtime
NewRuntime prepares a runtime.
func (*Runtime) ChannelStates ¶
ChannelStates reports which enabled channels exist and which are running.
Used by alerting to notice a channel that is configured, enabled, and not listening — which looks identical to a healthy channel on every other measure, because a channel that never started also never fails.
func (*Runtime) CheckContractsDue ¶
CheckContractsDue re-checks any channel whose interval has elapsed.
Driven from the alert loop rather than its own goroutine: one timer is easier to reason about than two, and it means a contract result is never newer or older than the reading it is attached to.
func (*Runtime) ContractDrift ¶
func (rt *Runtime) ContractDrift() map[string]alerts.ContractDrift
ContractDrift reports each channel's contract result for the alert path.
Returns the cached result rather than checking, because the alert loop runs every thirty seconds and profiling five hundred messages on that schedule would be load for no benefit.
func (*Runtime) EnableContracts ¶
EnableContracts turns on periodic contract checking.
Called by the server only when at least one channel has a contract, so an installation that uses none pays nothing - no goroutine, no state, no reads of the message store.
func (*Runtime) RestartUsingTables ¶ added in v0.1.17
func (rt *Runtime) RestartUsingTables(ctx context.Context, file string, log *slog.Logger) (restarted []string, failed map[string]string)
RestartUsingTables restarts every running channel that loads the named tables file, so a table change reaches them: a channel reads its tables when it starts. Each is stopped (waiting for the message in hand) and started again. It returns the channels restarted and, for any that would not start again, why.
func (*Runtime) Route ¶
Route hands a message to another running channel, satisfying engine.ChannelRouter.
The message goes in at the top of the target channel, so its filter, transformations and destinations all run while its source does not. A routed message is already inside the server; the source connector exists to receive from outside.
func (*Runtime) RunningChannels ¶
RunningChannels returns the channels currently started.
Copied under the lock rather than handing out the map, so a caller iterating it cannot race a channel being started or stopped underneath them.
func (*Runtime) StartEnabled ¶
StartEnabled starts every channel whose file says it is enabled.
func (*Runtime) States ¶
func (rt *Runtime) States() []ChannelState
States returns the live state of every channel on disk, running or not.
Never returns nil. The result is serialised straight into the status response, where the contract says a list, and a nil slice would marshal to null and crash a caller that trusted it. This returned nil when the channels directory could not be read - a mount dropping out, or permissions changing - which is exactly the moment the dashboard needs to stay up in order to say so.
func (*Runtime) TenantName ¶ added in v0.1.23
watchName is the name the access monitor knows a channel by: prefixed by the tenant on a shared server, where two customers may each have an "adt". TenantName is the tenant this runtime serves, "" on a single-tenant server, for what its senders record.
type SAMLConfig ¶
type SAMLConfig struct {
// EntityID identifies Perfuse to the identity provider, and is the audience the assertion must be addressed to.
EntityID string
// ACSURL is where the identity provider posts its response. It has to match what is registered there exactly.
ACSURL string
// IdPSSOURL is where the browser is sent to sign in.
IdPSSOURL string
// IdPCertPEM is the certificate that signs responses, and the only one trusted. A certificate carried inside a response is
// never used: see internal/saml/embeddedcert_test.go for what that would cost.
IdPCertPEM string
// GroupsAttribute names the assertion attribute carrying group membership.
//
// There is no standard for this and no useful default. Entra sends "groups", Okta usually sends whatever the administrator
// named in the app configuration, ADFS sends a claim URI. Guessing would produce a sign-in that works and grants nobody
// anything, which reads as a broken product rather than as a missing setting.
GroupsAttribute string
// Roles maps the provider's groups to Perfuse roles.
Roles *oidc.RoleMapping
// CreateUsers allows a first sign-in to create an account. False is the safe default, as with OIDC.
CreateUsers bool
// AllowUnsolicited accepts a response that answers no request this server sent, which is how a portal tile works. Off by
// default: it is also what lets a captured response be posted into somebody else's browser.
AllowUnsolicited bool
// Label is what the sign-in button says.
Label string
// SP is built at startup so a bad certificate is reported then rather than at somebody's first sign-in.
SP *saml.ServiceProvider
}
SAMLConfig is how SAML sign-in is configured.
Deliberately shaped like OIDCConfig, including reusing oidc.RoleMapping. The two protocols disagree about almost everything on the wire and agree completely about what an administrator has to decide: which directory, who is allowed in, which groups grant which role, and whether a first sign-in may create an account. Two different shapes for that would mean two screens to learn and two places for the same misconfiguration to hide.
func SAMLFromFile ¶
func SAMLFromFile(path string) (*SAMLConfig, error)
SAMLFromFile builds a runtime configuration from a file, for the server to use at startup.
Here rather than in cmd so that the assembly is tested: this is where a field can be read from the file and silently not passed on, which produces a setting that exists everywhere except where it matters.
func (*SAMLConfig) ButtonLabel ¶
func (c *SAMLConfig) ButtonLabel() string
ButtonLabel is what the sign-in button says.
func (*SAMLConfig) Enabled ¶
func (c *SAMLConfig) Enabled() bool
Enabled reports whether SAML sign-in is configured.
type SegmentView ¶
type SegmentView struct {
Index int `json:"index"`
Name string `json:"name"`
Description string `json:"description"`
Known bool `json:"known"`
Raw string `json:"raw"`
Fields []FieldView `json:"fields"`
}
SegmentView is one segment, explained.
type Server ¶
type Server struct {
// Channels manages the channel files on disk.
Channels *ChannelRepo
// Alerts is the alert evaluator, nil when alerting is off.
Alerts *alerts.Evaluator
// PHIWatch is the patient-data access monitor, nil when it is off (serve -watch-access=false).
PHIWatch *compliance.Monitor
// Terminology holds imported code systems and the values mapping tables did not have; nil without a database.
Terminology *terminology.Store
// PatientIndex is the master patient index; nil when the server keeps none (serve -mpi).
PatientIndex *mpi.Store
// Privacy answers data protection requests (GDPR, UK GDPR); nil without a database.
Privacy *privacy.Service
// Cluster is this server's place in a cluster of servers sharing one store; nil for a server on its own.
Cluster ClusterControl
// SNMP is the SNMP agent and trap sender, nil when SNMP is off.
SNMP *snmp.Service
// Store holds users, sessions and the audit log.
Store *store.Store
// Log receives request and error events.
Log *slog.Logger
// OIDC configures federated sign-in. Nil when it is not configured.
OIDC *OIDCConfig
// SAML is SAML 2.0 sign-in, nil when not configured.
SAML *SAMLConfig
// PasskeyRPID is the relying party identifier passkeys are registered for: a bare domain.
//
// This is what binds a credential to this site and makes it unphishable. Empty disables passkeys, because there is no
// safe default: guessing it from a request's Host header would let whoever controls DNS decide what a credential is
// bound to, which is the one thing the value exists to prevent.
PasskeyRPID string
// Demo is a shared sign-in shown on the sign-in page, for a public demonstration server. Nil: none.
Demo *DemoAccount
// PasskeyRPName is what the authenticator shows a person when they register.
PasskeyRPName string
// PasskeyOrigins are the exact origins accepted, scheme and port included.
PasskeyOrigins []string
// SCIMDefaultRole is the role an account gets when the identity provider does not say.
//
// Viewer unless an operator changes it, and the default is downward on purpose: an account whose role could not be
// determined should be able to read and nothing else. Defaulting upward would hand out administrator to anybody the
// provider failed to describe, and provisioning failures are exactly the case where a description goes missing.
SCIMDefaultRole store.Role
// SCIMEnabled turns the provisioning endpoints on.
//
// Off by default. These endpoints create and delete accounts, so an installation that is not being provisioned by an
// identity provider should not expose them at all - an endpoint nobody uses is one nobody is watching.
SCIMEnabled bool
// PasskeyAttestation is what an operator requires of an authenticator's own attestation.
//
// Zero verifies nothing and claims nothing, which is the right default: attestation answers "which device", not
// "is this legitimate", and the origin binding does the security work either way.
PasskeyAttestation webauthn.AttestationPolicy
// Restart asks the server to stop cleanly and start again, reading its settings afresh. Nil when the process cannot
// restart itself, and the console then says so rather than offering a button that does nothing.
Restart func()
// MetricsAccessFn supplies the configured metrics access mode, superseding MetricsOpen when set.
//
// A function so the setting applies without a restart, read at request time. Nil falls back to the flags.
MetricsAccessFn func() string
// MetricsToken is a dedicated credential for the Prometheus scrape endpoint.
//
// Separate from an API token because a scraper cannot sign in, cannot hold a cookie and should not hold a
// credential that also permits writes. Empty means no scrape token is configured, and then only a signed-in
// operator can read metrics.
MetricsToken string
// MetricsOpen serves metrics to anybody who can reach the port.
//
// Was the only behaviour, and remains available because plenty of installations really do have the port bound to a
// private interface. Named rather than implied so it shows up in the command line of any server running this way:
// "we thought it was firewalled" is how this turns into a finding.
MetricsOpen bool
// SettingsPaths are the configuration files the interface may edit.
//
// Paths rather than parsed values, because the interface edits the same files the flags point at. Keeping a second
// copy of these settings in the database would raise the question of which one wins, and the answer would be
// discovered during an incident.
SettingsPaths SettingsFiles
// Startup describes what the process was started with, for the read-only half of the settings page.
//
// Shown rather than hidden. Somebody diagnosing a problem needs to know how the process was started, and "not
// shown anywhere" is how a wrong flag survives for months.
Startup StartupSettings
// Started is when this process began, for the settings page.
Started time.Time
// LDAP configures directory sign-in. Nil when it is not configured.
//
// Separate from OIDC rather than one "external identity" field, because both can be configured at once and they
// answer different problems: OIDC needs a browser that can reach the provider, and a directory does not.
LDAP *LDAPConfig
// SecureCookies marks the session cookie Secure. It should be on whenever
// the server is reachable over anything but plain localhost.
SecureCookies bool
// StaticHandler serves the built front end, if one is embedded.
StaticHandler http.Handler
// Repos hands out a channel repository per tenant. Nil in single-tenant
// operation, where Channels is used directly.
Repos *TenantRepos
// Runtimes hands out one engine per tenant, nil in single-tenant operation.
//
// When set it takes precedence over Runtime, and handlers must reach engines
// through runtimeFor rather than reading Runtime - a test enforces that,
// because it is the mistake review cannot catch across sixty handlers.
Runtimes *TenantRuntimes
// Runtime owns the running engine. When nil, the interface can edit channels
// but not run them, and the endpoints that need it say so rather than
// pretending.
Runtime *Runtime
// Version is reported by the probe endpoints. During a rollback, which build
// is answering is the only question anybody has.
Version string
// Settings is every option an operator may change, and the file they live in.
//
// Nil on a server started without one, which is legitimate: the engine runs on declared defaults and the settings
// area says it cannot save rather than presenting a form whose button fails.
Settings *settings.Store
// Branding holds a customer's logo, so an installation can present itself as their product.
//
// Optional: nil means the built-in mark, and an upload is refused rather than kept in memory.
Branding *branding.Store
// FleetLabel names this instance in a fleet view. Empty means the hostname.
//
// Configured rather than taken from anything in a message, following the rule
// that user-visible labels never come from message content.
FleetLabel string
// PeersFile is where fleet peers are stored, and empty when this server has nowhere to put them.
//
// Needed because peers can now be added from the interface. Without a path the Fleet section says so rather
// than offering a form whose save would fail.
PeersFile string
// SelfURL is this server's own address, used only to refuse a peer that points back here.
//
// A self-referencing peer polls this instance through its own HTTP stack and then appears twice in the fleet
// view - once as this server and once as a peer - which reads as two machines and is one.
SelfURL string
// PublicURL is where patients' phones and other organisations reach this server, for the SMART Health Links it hosts. Defaults to
// SelfURL, which is right only when the two are the same machine name.
PublicURL string
// SHLAllowHTTP lets a received SMART Health Link point at plain HTTP. Off: only tests and a laboratory want it.
SHLAllowHTTP bool
// Dashboards says which dashboard each role and directory group opens on (-dashboards). Nil: everyone opens on operations.
Dashboards *dashboards.Assignments
// ECRTriggers are the trigger codes the case report preview uses: the RCTC when the server was given one (-rctc), else nil
// for the built-in sample.
ECRTriggers *publichealth.TriggerSet
// CRD is the Da Vinci Coverage Requirements Discovery rules, when the payer's CDS service is on.
CRD *crd.Rules
// PAS is the FHIR server answering Da Vinci PAS, for the reviewer queue; nil or without PAS, the queue says it is off.
PAS *fhirserver.Server
// SMART is the built-in SMART authorization server, and the files its apps and people are kept in, which Users →
// SMART apps edits. Nil without -smart-clients.
SMART *smartauth.Server
SMARTClientsFile string
SMARTUsersFile string
// VPN reads the partner tunnels listed in -vpn; nil without it.
VPN *vpn.Monitor
// DTRPackage runs this server's own DTR $questionnaire-package on a Parameters body, for the console. Nil when no FHIR endpoint
// is served.
DTRPackage func(ctx context.Context, params []byte) (status int, body []byte)
// DICOMweb is the imaging archive served at /dicomweb, nil without -dicomweb-archive; DICOMwebCORS are the browser
// origins (a viewer such as OHIF served elsewhere) allowed to call it.
DICOMweb *dicomweb.Archive
DICOMwebCORS []string
// Drugs is the formulary, Real-Time Prescription Benefit and NCPDP SCRIPT ePA, nil without -formulary.
Drugs *Drugs
// XDS is the IHE XDS.b registry and repository and XCA responding gateway, nil without -xds. FHIRStore is this server's
// FHIR store, which MHD resolves patients from and PIXm and PDQm answer from; nil without -fhir.
XDS *xds.Registry
// DirectDir is the Direct address book: one PEM file per correspondent, which a direct destination's recipient_certs_dir
// reads (and which it reads by default).
DirectDir string
// ViewsDir keeps SQL on FHIR ViewDefinitions; RunView runs one over the FHIR store (nil without -fhir).
ViewsDir string
RunView ViewRunner
// Measures evaluates digital quality measures over the FHIR store (nil without -fhir).
Measures MeasureService
FHIRStore *fhirserver.Store
// MCP is the Model Context Protocol server at /mcp, off unless its Enabled says so. FHIRHandler is the FHIR endpoint
// (with /fhir stripped), which MCP's fhir_search calls with the agent's own token; nil without -fhir.
MCP *MCPSettings
FHIRHandler http.Handler
// PatientSummary builds this server's own International Patient Summary for a patient, for Health links. Nil when
// no FHIR endpoint is served.
PatientSummary func(ctx context.Context, patientID string) (status int, body []byte)
// CDSClients are the EHRs trusted to call it with signed JWTs.
CDSClients []*CDSClient
// TLSCertFile and TLSKeyFile are the server's own certificate and key, when it was started with them.
//
// Held as paths rather than as a loaded key pair so they can be read at the moment they are used. Signing is
// rare, the files are small, and a cached key means a replaced certificate keeps producing signatures against
// the old one until somebody restarts - with nothing on screen to explain why.
TLSCertFile string
TLSKeyFile string
// TSAURL is the RFC 3161 time-stamping authority for DSDR signatures on claims attachments. Without one a signature stops
// short of the XAdES-X-L the guide asks for, and says so.
TSAURL string
// CMS0057 describes the FHIR endpoint, so the CMS-0057 page can say which payer APIs this instance can answer.
CMS0057 CMS0057Status
// TEFCA is this instance's participation in national exchange, nil when it does not participate.
//
// Held as a Participant rather than as a configuration, because a Participant cannot exchange without auditing -
// there is no unaudited path through it, so nothing here has to remember to record anything.
TEFCA *tefca.Participant
// TEFCAAudit is the trail, kept separately so the audit view can read the file rather than only memory.
TEFCAAudit *tefca.PersistentAuditLog
// Fleet polls other instances, nil when no peers are configured.
//
// Any instance can be the aggregator, because the console ships inside every
// binary. Mirth needed a separate product for this - a plug-in registering
// with a Command Center server - because its administrator is a desktop
// application and there was nowhere in that design to put a fleet view.
Fleet *peers.Fleet
// contains filtered or unexported fields
}
Server is the HTTP API.
func (*Server) BeginDraining ¶
func (s *Server) BeginDraining()
BeginDraining marks the instance as no longer accepting new traffic.
Called at the top of shutdown, before anything is actually stopped, so there is a window in which the load balancer can notice and stop routing while the process is still perfectly able to finish what it already accepted.
func (*Server) MarkReady ¶
func (s *Server) MarkReady()
MarkReady records that startup finished and channels have been started.
Until this is called, readiness answers no. Reporting ready before channels are up would let a load balancer send messages to an instance with nothing listening, which presents to the sender as a connection refused rather than as a deploy in progress.
func (*Server) OutboundClient ¶ added in v0.1.6
shlClient fetches a stranger's link. The URL comes from a QR code, so the dial is held to the egress policy at connect time - after DNS, so a name that resolves to a metadata address is caught - and plain HTTP is refused unless the server was told otherwise. OutboundClient is the client this server uses to reach other systems on a user's or EHR's behalf: egress rules apply, and redirects are limited.
func (*Server) Routes ¶
func (s *Server) Routes() []routeRecord
Routes returns every route this server registers, with the role each requires.
Exported so a test can assert over the whole surface rather than over a list of what somebody remembered to write down. Building the handler is cheap and its only side effect is the recording.
type SettingsFiles ¶
type SettingsFiles struct {
// Alerts is the alert rules file, from -alerts.
Alerts string
// OIDC is the single sign-on configuration, from -oidc.
OIDC string
// LDAP is the directory configuration, from -ldap.
LDAP string
// SAML is the SAML 2.0 sign-in configuration, from -saml.
SAML string
// Peers is the fleet peer list, from -peers.
Peers string
}
SettingsFiles are the configuration files the interface may edit.
Paths come from the flags the process was started with. The interface edits the same files rather than keeping a second copy in the database, which keeps configuration diffable on disk and avoids the question of which source wins.
A file that was not configured has an empty path, and the interface offers to create one rather than silently having nowhere to write.
type StartupSettings ¶
type StartupSettings struct {
Addr string `json:"addr"`
ChannelsDir string `json:"channelsDir"`
DatabasePath string `json:"databasePath"`
TLS bool `json:"tls"`
MultiTenant bool `json:"multiTenant"`
EngineRunning bool `json:"engineRunning"`
FHIRServing bool `json:"fhirServing"`
StoreMessages bool `json:"storeMessages"`
StorePayloads bool `json:"storePayloads"`
RetentionDays int `json:"retentionDays"`
AllowMetadata bool `json:"allowMetadataEgress"`
JSONLogs bool `json:"jsonLogs"`
Version string `json:"version"`
GoVersion string `json:"goVersion"`
Platform string `json:"platform"`
StartedAt string `json:"startedAt"`
CommandLine []string `json:"commandLine"`
FleetLabel string `json:"fleetLabel,omitempty"`
TraceEndpoint string `json:"traceEndpoint,omitempty"`
AlertWebhook bool `json:"alertWebhook"`
AlertSeverity string `json:"alertSeverity,omitempty"`
AuthMethods []string `json:"authMethods"`
MetadataReason string `json:"metadataReason,omitempty"`
}
StartupSettings are what the process was started with.
Read-only, and shown rather than hidden. Somebody diagnosing a problem needs to know how the process was started, and "not shown anywhere" is how a wrong flag survives for months.
type TenantRepos ¶
type TenantRepos struct {
// contains filtered or unexported fields
}
TenantRepos hands out one ChannelRepo per tenant.
Channels stay in files. That is the invariant everything else in this program rests on, and multi-tenancy does not get to break it - it just means a directory per tenant instead of one directory.
The layout, and why it is asymmetric ¶
<base>/*.yaml the default tenant's channels <base>/<tenant>/*.yaml any other tenant's channels
The default tenant uses the base directory itself rather than <base>/main. That asymmetry is deliberate and it is the whole point: every installation running today points -channels at a directory of loose yaml files. Requiring them to become <base>/main/*.yaml would make every existing channel vanish on upgrade, and an operator discovering that has already lost their feeds.
It works because ChannelRepo.files() skips directories and does not recurse, so the default tenant cannot see another tenant's subdirectory. That is load-bearing rather than incidental, and there is a test asserting it.
func NewTenantRepos ¶
func NewTenantRepos(base string) (*TenantRepos, error)
NewTenantRepos roots a set of per-tenant repositories at base.
func (*TenantRepos) DirFor ¶
func (tr *TenantRepos) DirFor(id tenant.ID) (string, error)
DirFor returns the directory a tenant's channels live in.
Exported because the error messages elsewhere are much more useful when they can name the path somebody needs to look at.
func (*TenantRepos) Forget ¶
func (tr *TenantRepos) Forget(id tenant.ID)
Forget drops a cached repository.
Called when a tenant is deleted. The directory is deliberately left on disk: a channel file is a definition somebody wrote, frequently the only record of how an interface was configured, and deleting a customer should not silently destroy it. Removing the directory is a separate, explicit act.
func (*TenantRepos) Known ¶
func (tr *TenantRepos) Known() ([]tenant.ID, error)
Known returns the tenants that currently have a directory on disk.
Reads the filesystem rather than the cache, because the answer wanted is "what is actually there", which is what somebody investigating a missing channel needs.
func (*TenantRepos) Repo ¶
func (tr *TenantRepos) Repo(id tenant.ID) (*ChannelRepo, error)
Repo returns the repository for a tenant, creating the directory on first use.
type TenantRuntimes ¶
type TenantRuntimes struct {
// LimitsFor reads a tenant's limits. Nil: no limits are enforced.
LimitsFor func(context.Context, tenant.ID) (tenant.Limits, error)
// PHIWatch is the server's patient-data access monitor, given to every tenant's runtime; nil watches nothing.
// Its channel names carry the tenant (watchName), so one tenant's traffic is never another's baseline.
PHIWatch *compliance.Monitor
// contains filtered or unexported fields
}
TenantRuntimes hands out one engine per tenant.
This is the half of tenancy that channel isolation did not cover. Isolating the channel *files* stopped one tenant reading another's definitions; it did nothing about the engine, so start and stop, metrics, the queue and the message store were all shared. One tenant stopping a channel could stop another's, and the metrics endpoint reported the sum of everybody.
Runtimes are created on demand rather than up front, because a platform with fifty tenants where three are active should not hold fifty engines, and a tenant that never signs in should cost nothing.
func NewTenantRuntimes ¶
func NewTenantRuntimes( repos *TenantRepos, messages *msgstore.Store, fhirStore *fhirserver.Store, collector *metrics.Collector, log *slog.Logger, ) *TenantRuntimes
NewTenantRuntimes prepares the registry.
func (*TenantRuntimes) For ¶
func (tr *TenantRuntimes) For(id tenant.ID) (*Runtime, error)
For returns the runtime for a tenant, creating it if this is the first request.
func (*TenantRuntimes) StopAll ¶
func (tr *TenantRuntimes) StopAll(ctx context.Context)
StopAll stops every tenant's channels.
Called on shutdown. Sequential rather than concurrent: shutdown already has a deadline, and a hundred engines draining queues in parallel makes the slowest one slower rather than the whole thing faster.
func (*TenantRuntimes) StoredBytes ¶ added in v0.1.11
StoredBytes is how much message content a tenant holds, for the platform's list of tenants and their limits.
type Trend ¶
type Trend struct {
// Verdicts is the window, oldest first, for a sparkline.
Verdicts []contractVerdict `json:"verdicts"`
// Checks is how many are in the window, and Failing how many of those did not hold. A rate is unreadable
// without both.
Checks int `json:"checks"`
Failing int `json:"failing"`
// ChangedAt is when the current outcome first appeared, or nil when the whole window agrees. This is the answer
// to "when did this start", which is the first question after "what changed".
ChangedAt *time.Time `json:"changedAt,omitempty"`
// ObservedSince is when this instance started watching. The interface needs it to avoid claiming a duration it
// cannot support: if ChangedAt equals the first entry, the change may well have happened before we were
// looking.
ObservedSince time.Time `json:"observedSince"`
// FromStartOfWindow is true when the current outcome runs all the way back to the oldest entry, meaning the
// real change happened earlier than anything recorded - so a duration derived from ChangedAt is a floor, not a
// measurement.
FromStartOfWindow bool `json:"fromStartOfWindow"`
// Flapping reports the outcome changing repeatedly rather than once. It matters because a feed alternating
// between holding and failing is a different problem from one that broke and stayed broken, and the same
// number of failures describes both.
Flapping bool `json:"flapping"`
}
Trend describes a channel's recent contract behaviour.
type ValidationFailure ¶
type ValidationFailure struct {
Problems []string
}
ValidationFailure carries every problem found in a definition, so the front end can show them all at once.
func (*ValidationFailure) Error ¶
func (e *ValidationFailure) Error() string
Source Files
¶
- accesswatch.go
- alertrules.go
- alerts.go
- alignednetwork.go
- authenticate.go
- branding.go
- build.go
- cdamerge.go
- cdapdf.go
- cdareconcile.go
- cdarepair.go
- cdasign.go
- cdshooks.go
- certificates.go
- channels.go
- channelscope.go
- channelsfor.go
- cluster.go
- cms0057.go
- codesets.go
- connections.go
- contractbuild.go
- contractcheck.go
- contracthistory.go
- contracts.go
- dashboards.go
- demo.go
- dicomweb.go
- direct.go
- drugs.go
- eicr.go
- eligibility.go
- figures.go
- fleet.go
- fleetpeers.go
- fleetself.go
- flow.go
- friction.go
- fromsample.go
- generate.go
- handlers.go
- health.go
- history.go
- identitysearch.go
- ldap.go
- manual.go
- mapping.go
- mappingapply.go
- mcp.go
- measures.go
- metricsauth.go
- mhd.go
- mirthexport.go
- mirthimport.go
- mpi.go
- neterr_unix.go
- observe.go
- oidc.go
- parity.go
- parse.go
- pasreview.go
- passkeys.go
- payer.go
- pixv3.go
- priorauth.go
- privacy.go
- profile.go
- profileshare.go
- queue.go
- redact.go
- replay.go
- restart.go
- routes.go
- runtime.go
- saml.go
- samlmetadata.go
- samlsignon.go
- scim.go
- scriptcheck.go
- search.go
- server.go
- settings.go
- settingslive.go
- settingsschema.go
- settingsvalidate.go
- shadow.go
- shadowsave.go
- shl.go
- signon.go
- signonfields.go
- smart.go
- snmp.go
- spec.go
- storefor.go
- tablewrite.go
- tefca.go
- tenantrepos.go
- tenantruntimes.go
- tenants.go
- terminology.go
- tokens.go
- trace.go
- v3fields.go
- validate.go
- views.go
- vpn.go
- xds.go