Documentation
¶
Overview ¶
Package contract defines the declarative, single-endpoint contract for the admin dashboard: contributor manifests, request/response envelopes, the permission model, the slot/graph composition rules, and the per-contributor version negotiation protocol.
See DESIGN.md in this directory for the spec this implements.
envelope.go
graph.go
manifest.go
registry.go
slots.go
Index ¶
- Constants
- Variables
- func PermissionsHash(user *dashauth.UserInfo) string
- func UnmarshalManifestForTest(b []byte, m *ContractManifest) error
- type Action
- type AppInfo
- type AuditEmitter
- type AuditFilter
- type AuditRecord
- type AuditStore
- type CacheHint
- type Capability
- type Cardinality
- type ContractManifest
- type Contributor
- type DataBinding
- type Decision
- type Deprecation
- type EnvelopeSupport
- type Error
- type ErrorCode
- type ErrorResponse
- type Extension
- type ExtensionTarget
- type GraphBuilder
- type GraphCache
- type GraphCacheKey
- type GraphNode
- type Intent
- type IntentKind
- type IntentKindDef
- type IntentSchema
- type Kind
- type NavConfig
- type NoopAuditEmitter
- type ParamSource
- type Predicate
- type Principal
- type Query
- type QueryCache
- type Registry
- type RemoteEndpoint
- type Request
- type RequestContext
- type Response
- type ResponseMeta
- type SlotDef
- type StreamEvent
- type SubscriptionMode
- type Warden
- type WardenRegistry
Constants ¶
const MaxSlotDepth = 8
MaxSlotDepth is the maximum nesting depth of graph nodes; trees deeper than this are rejected at registration.
Variables ¶
var ( ErrBadRequest = &Error{Code: CodeBadRequest} ErrUnauthenticated = &Error{Code: CodeUnauthenticated} ErrPermissionDenied = &Error{Code: CodePermissionDenied} ErrNotFound = &Error{Code: CodeNotFound} ErrConflict = &Error{Code: CodeConflict} ErrRateLimited = &Error{Code: CodeRateLimited} ErrUnsupportedVersion = &Error{Code: CodeUnsupportedVersion} ErrInternal = &Error{Code: CodeInternal} )
Sentinel errors for use with errors.Is.
var DefaultSlotCatalog = map[string]IntentKindDef{ "page.shell": { Slots: map[string]SlotDef{ "header": { Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne, }, "main": { Accepts: pageMainIntents, Cardinality: CardinalityMany, }, }, }, "resource.list": { Slots: map[string]SlotDef{ "rowActions": {Accepts: []string{"action.button", "action.menu", "action.divider"}, Cardinality: CardinalityMany}, "detailDrawer": {Accepts: []string{"form.edit", "resource.detail", "custom"}, Cardinality: CardinalityOne, Extensible: true}, "filters": { Accepts: []string{ "filter.search", "filter.select", "filter.date", "molecule.search-bar", "molecule.combobox", "molecule.date-picker", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, "bulkActions": { Accepts: []string{"action.button", "action.menu"}, Cardinality: CardinalityMany, Extensible: true, }, "header": { Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne, }, "pagination": { Accepts: []string{"molecule.pagination", "pagination.cursor", "pagination.offset"}, Cardinality: CardinalityOne, }, }, }, "resource.detail": { Slots: map[string]SlotDef{ "header": { Accepts: []string{"detail.header", "layout.section", "atom.heading", "molecule.breadcrumb", "custom"}, Cardinality: CardinalityOne, }, "sections": { Accepts: []string{ "detail.section", "layout.section", "layout.card", "layout.tabs", "layout.accordion", "resource.list", "resource.detail", "form.edit", "organism.data-grid", "organism.dynamic-form", "organism.timeline", "organism.chart", "molecule.alert", "molecule.empty-state", "audit.tail", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, "actions": { Accepts: []string{"action.button", "action.menu", "action.divider", "atom.button", "confirm.dialog"}, Cardinality: CardinalityMany, Extensible: true, }, "extensions": { Accepts: []string{ "detail.section", "layout.section", "layout.card", "resource.list", "resource.detail", "form.edit", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, }, }, "dashboard.grid": { Slots: map[string]SlotDef{ "widgets": { Accepts: []string{ "metric.counter", "metric.gauge", "audit.tail", "dashboard.stat", "dashboard.recentlist", "molecule.stat-card", "organism.chart", "organism.timeline", "organism.calendar", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, }, }, "form.edit": { Slots: map[string]SlotDef{ "fields": {Accepts: formFieldIntents, Cardinality: CardinalityMany, Extensible: true}, "actions": { Accepts: []string{"action.button", "atom.button"}, Cardinality: CardinalityMany, Extensible: true, }, "sections": { Accepts: []string{"layout.section", "layout.card"}, Cardinality: CardinalityMany, }, }, }, "auth.login.form": {Slots: map[string]SlotDef{}}, "auth.signin-form": {Slots: map[string]SlotDef{}}, "auth.signup-form": {Slots: map[string]SlotDef{}}, "auth.magic-link-form": {Slots: map[string]SlotDef{}}, "auth.oauth-buttons": {Slots: map[string]SlotDef{}}, "auth.forgot-password-form": {Slots: map[string]SlotDef{}}, "auth.reset-password-form": {Slots: map[string]SlotDef{}}, "auth.setup-form": {Slots: map[string]SlotDef{}}, "auth.dynamic-signup-form": {Slots: map[string]SlotDef{}}, "auth.two-factor-setup": {Slots: map[string]SlotDef{}}, "auth.passkey-prompt": {Slots: map[string]SlotDef{}}, "auth.session-list": {Slots: map[string]SlotDef{}}, "auth.user-button": {Slots: map[string]SlotDef{}}, "auth.account-menu": {Slots: map[string]SlotDef{}}, "auth.org-switcher": {Slots: map[string]SlotDef{}}, "auth.org-profile": {Slots: map[string]SlotDef{}}, "auth.tabs": { Slots: map[string]SlotDef{ "tabs": { Accepts: []string{ "auth.signin-form", "auth.signup-form", "auth.magic-link-form", "auth.oauth-buttons", "auth.passkey-prompt", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, }, }, "layout.tabs": { Slots: map[string]SlotDef{ "panels": { Accepts: []string{ "layout.section", "layout.card", "layout.stack", "resource.list", "resource.detail", "form.edit", "organism.data-grid", "organism.dynamic-form", "organism.chart", "custom", }, Cardinality: CardinalityMany, Extensible: true, }, }, }, "layout.accordion": { Slots: map[string]SlotDef{ "items": { Accepts: []string{"layout.section", "layout.card", "custom"}, Cardinality: CardinalityMany, Extensible: true, }, }, }, "layout.card": { Slots: map[string]SlotDef{ "header": {Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne}, "content": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}, "footer": {Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne}, }, }, "layout.section": { Slots: map[string]SlotDef{ "content": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}, }, }, "layout.page": { Slots: map[string]SlotDef{ "header": {Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne}, "body": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}, "footer": {Accepts: inlineAdornmentIntents, Cardinality: CardinalityOne}, }, }, "layout.stack": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "layout.row": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "layout.column": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "layout.grid": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "layout.container": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "layout.split": {Slots: map[string]SlotDef{"children": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}}}, "detail.header": {Slots: map[string]SlotDef{}}, "detail.section": { Slots: map[string]SlotDef{ "content": {Accepts: broadContentIntents, Cardinality: CardinalityMany, Extensible: true}, }, }, "editor.code": {Slots: map[string]SlotDef{}}, "editor.formBuilder": {Slots: map[string]SlotDef{}}, "confirm.dialog": { Slots: map[string]SlotDef{ "trigger": {Accepts: []string{"action.button", "atom.button"}, Cardinality: CardinalityOne}, "body": {Accepts: []string{"layout.section", "atom.text", "form.edit", "custom"}, Cardinality: CardinalityMany}, }, }, "metric.gauge": {Slots: map[string]SlotDef{}}, "dashboard.stat": {Slots: map[string]SlotDef{}}, "dashboard.recentlist": {Slots: map[string]SlotDef{}}, "filter.search": {Slots: map[string]SlotDef{}}, "filter.select": {Slots: map[string]SlotDef{}}, "filter.date": {Slots: map[string]SlotDef{}}, "pagination.cursor": {Slots: map[string]SlotDef{}}, "pagination.offset": {Slots: map[string]SlotDef{}}, "field.json": {Slots: map[string]SlotDef{}}, "field.permissions": {Slots: map[string]SlotDef{}}, "settings.tabs": {Slots: map[string]SlotDef{}}, "settings.panel": {Slots: map[string]SlotDef{}}, "feature.toggles": {Slots: map[string]SlotDef{}}, }
DefaultSlotCatalog is the v1 catalog of built-in intent kinds and their slots. Adding a new built-in intent kind here is a shell-version bump (adds new renderer behavior). The catalog mirrors the React renderer registry in /shell/src/intents/register.ts — every intent kind exposed to manifest authors should appear in both places.
Functions ¶
func PermissionsHash ¶
PermissionsHash returns a stable, order-independent hash of a user's roles and scopes. Used as part of the graph cache key so that users with the same effective permissions share a cache entry. Claims are NOT included because the contract treats only role/scope as graph-shape-determining.
func UnmarshalManifestForTest ¶
func UnmarshalManifestForTest(b []byte, m *ContractManifest) error
UnmarshalManifestForTest is a test helper exposed for use by sibling packages. It is not part of the package's runtime API; production code should not call it.
Types ¶
type Action ¶
type Action struct {
Contributor string
Intent string
Kind Kind
Capability Capability
Resource map[string]any
}
Action is the operation being authorized. Kind is the wire-side envelope discriminator (graph/query/command/subscribe) so HTTP and SSE callers can pass req.Kind directly. Note this is the wire Kind, not the manifest's IntentKind — the values mostly overlap but "subscription" (manifest) is "subscribe" (wire).
type AppInfo ¶ added in v1.6.6
type AppInfo struct {
DisplayName string `yaml:"displayName" json:"displayName"`
Slug string `yaml:"slug,omitempty" json:"slug,omitempty"`
Root bool `yaml:"root,omitempty" json:"root,omitempty"`
Icon string `yaml:"icon,omitempty" json:"icon,omitempty"`
Priority int `yaml:"priority,omitempty" json:"priority,omitempty"`
Home string `yaml:"home,omitempty" json:"home,omitempty"`
}
AppInfo describes how a contributor presents itself in the app switcher. All fields are display-only — the contract dispatch path is unaffected.
contributor:
name: core-contract
app:
displayName: Forge
root: true # this app owns the bare URL; no /@slug prefix
icon: forge
priority: 0
home: /
contributor:
name: auth
app:
displayName: Authsome
slug: authsome # routes become /@authsome/...
icon: shield
priority: 10
home: /users
Root marks the platform app: its routes are NOT URL-prefixed, so /, /health, etc. stay bare. There must be at most one root app per dashboard deployment (the registry doesn't enforce this today; behaviour on conflict is "first registered wins" via the natural ordering in apps.list).
Slug controls URL namespacing for non-root apps: when set, every top-level graph route the contributor declares is projected to /@<slug><route> on the wire, and Home is projected the same way. Defaults to Contributor.Name when unset. Has no effect on a root app — root URLs are always bare regardless of slug.
func (*AppInfo) ResolvedSlug ¶ added in v1.6.6
ResolvedSlug returns the slug that should be used for URL prefixing, falling back to the contributor name when no explicit slug is set. Returns "" for root apps so callers can rely on a non-empty slug signalling "this app gets URL prefixing" without a separate `if app.Root` branch.
type AuditEmitter ¶
type AuditEmitter interface {
Emit(ctx context.Context, rec AuditRecord)
}
AuditEmitter ships audit records to durable storage. Slice (b) wires the chronicle implementation; slice (a) ships log-based and noop variants.
func NewLogAuditEmitter ¶
func NewLogAuditEmitter(w io.Writer) AuditEmitter
NewLogAuditEmitter returns an emitter that writes a stable line format to w. Suitable for development and as a fallback when no chronicle backend is wired.
func NewRecordingAuditEmitter ¶
func NewRecordingAuditEmitter(inner AuditEmitter, store AuditStore) AuditEmitter
NewRecordingAuditEmitter returns an emitter that fans out to inner (typically the log emitter) and also persists to store. Either may be nil; both nil is a noop.
type AuditFilter ¶
AuditFilter narrows audit.list results. All fields are optional. Limit is clamped to [1, 1000]; zero defaults to 200.
type AuditRecord ¶
type AuditRecord struct {
Time time.Time
Contributor string
Intent string
IntentVersion int
Subject string // resource id when known
User string // user identity (subject from UserInfo)
Result string // ok | error
LatencyMs int64
Payload map[string]any // pre-redaction; subject to per-intent redaction list
CorrelationID string
}
AuditRecord is one auditable command invocation.
type AuditStore ¶
type AuditStore interface {
Append(rec AuditRecord)
List(filter AuditFilter) []AuditRecord
// Subscribe returns a channel that receives every Append from now on, plus
// a cancel func that closes the channel and unregisters the subscriber.
// Slow subscribers drop events rather than block writers — audit is
// telemetry, not the source of truth.
Subscribe() (<-chan AuditRecord, func())
}
AuditStore is the persistent (process-local for slice (k)) view of audit records. It exists to back the audit.list query and audit.tail subscription the dashboard exposes; production deployments swap the in-memory impl for a durable backend when one is wired.
func NewInMemoryAuditStore ¶
func NewInMemoryAuditStore(cap int) AuditStore
NewInMemoryAuditStore returns a store that keeps the most recent `cap` records in a ring buffer. cap <= 0 defaults to 1000.
type CacheHint ¶
type CacheHint struct {
StaleTime string `json:"staleTime,omitempty"`
}
CacheHint communicates how long the shell can serve stale data for a query.
type Capability ¶
type Capability string
Capability is the data-classification of an intent's effects. It composes with IntentKind: a command must be capability=write; a query/subscription must be capability=read; a graph must be capability=render.
const ( CapRead Capability = "read" CapWrite Capability = "write" CapRender Capability = "render" )
type Cardinality ¶
type Cardinality string
Cardinality describes how many fills a slot accepts.
const ( CardinalityOne Cardinality = "one" CardinalityMany Cardinality = "many" )
type ContractManifest ¶
type ContractManifest struct {
SchemaVersion int `yaml:"schemaVersion" json:"schemaVersion"`
Contributor Contributor `yaml:"contributor" json:"contributor"`
Queries map[string]Query `yaml:"queries,omitempty" json:"queries,omitempty"`
Intents []Intent `yaml:"intents" json:"intents"`
Graph []GraphNode `yaml:"graph,omitempty" json:"graph,omitempty"`
Extends []Extension `yaml:"extends,omitempty" json:"extends,omitempty"`
}
ContractManifest is the top-level YAML each contributor publishes.
type Contributor ¶
type Contributor struct {
Name string `yaml:"name" json:"name"`
Envelope EnvelopeSupport `yaml:"envelope" json:"envelope"`
Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
App *AppInfo `yaml:"app,omitempty" json:"app,omitempty"`
}
Contributor names a single contributor and declares its supported envelope versions.
App, when set, opts this contributor into the dashboard's app switcher. A contributor without an App block is a "library" contributor — it may declare intents and inject nodes into other contributors' graphs via Extends, but its own routes (if any) won't appear as a switchable app in the sidebar header. The pilot and authsome both set App so they surface as first-class apps; helper contributors (e.g. a future shared "design system" contributor) can stay invisible.
type DataBinding ¶
type DataBinding struct {
QueryRef string `yaml:"-" json:"queryRef,omitempty"`
Intent string `yaml:"intent,omitempty" json:"intent,omitempty"`
Kind IntentKind `yaml:"-" json:"kind,omitempty"`
Params map[string]ParamSource `yaml:"params,omitempty" json:"params,omitempty"`
}
DataBinding is either an inline {intent, params} pair or a named query reference. YAML supports both shapes:
data: queries.userList
data: { intent: users.list, params: {...} }
Kind is not authored in YAML; it's stamped at merge time from the referenced intent's declared kind so the React shell can pick the right hook (useContractQuery for query, useSubscription for subscription) without having to chase the manifest's intent table client-side.
func (*DataBinding) UnmarshalYAML ¶
func (d *DataBinding) UnmarshalYAML(value *yaml.Node) error
UnmarshalYAML accepts either a scalar (treated as a named query reference) or a mapping with the inline {intent, params} form.
type Decision ¶
type Decision struct {
// Allow reports whether access is granted.
Allow bool
// Reason is a short, human-readable explanation. Surfaced in audit logs
// and (optionally) in error responses.
Reason string
// Redactions lists JSONPath-like field paths that must be redacted from
// the response payload even when Allow is true. Empty when no redactions
// apply.
Redactions []string
}
Decision is the Warden's verdict.
type Deprecation ¶
type Deprecation struct {
IntentVersion int `json:"intentVersion"`
RemoveAfter string `json:"removeAfter"`
}
Deprecation surfaces a "this version will be removed" hint to the shell.
type EnvelopeSupport ¶
type EnvelopeSupport struct {
Supports []string `yaml:"supports" json:"supports"`
Preferred string `yaml:"preferred" json:"preferred"`
}
EnvelopeSupport declares which envelope versions this contributor can speak.
type Error ¶
type Error struct {
Code ErrorCode `json:"code"`
Message string `json:"message,omitempty"`
Details map[string]any `json:"details,omitempty"`
Retryable bool `json:"retryable,omitempty"`
CorrelationID string `json:"correlationID,omitempty"`
Redactions []string `json:"redactions,omitempty"`
}
Error is the canonical contract error type. It serializes to the wire "error" object documented in DESIGN.md.
type ErrorCode ¶
type ErrorCode string
ErrorCode is a canonical, wire-stable code for contract errors. Contributor-specific codes are namespaced like "auth.SESSION_EXPIRED".
const ( CodeBadRequest ErrorCode = "BAD_REQUEST" CodeUnauthenticated ErrorCode = "UNAUTHENTICATED" CodePermissionDenied ErrorCode = "PERMISSION_DENIED" CodeNotFound ErrorCode = "NOT_FOUND" CodeConflict ErrorCode = "CONFLICT" CodeRateLimited ErrorCode = "RATE_LIMITED" CodeUnsupportedVersion ErrorCode = "UNSUPPORTED_VERSION" CodeInternal ErrorCode = "INTERNAL" )
type ErrorResponse ¶
type ErrorResponse struct {
OK bool `json:"ok"`
Envelope string `json:"envelope"`
Error *Error `json:"error"`
}
ErrorResponse is the wire envelope for failed POST responses.
type Extension ¶
type Extension struct {
Target ExtensionTarget `yaml:"target" json:"target"`
Slot string `yaml:"slot" json:"slot"` // dotted path: "detailDrawer.fields"
Add []GraphNode `yaml:"add" json:"add"`
}
Extension declares that this contributor wants to add nodes into another contributor's slot.
type ExtensionTarget ¶
type ExtensionTarget struct {
Contributor string `yaml:"contributor" json:"contributor"`
Intent string `yaml:"intent" json:"intent"`
Route string `yaml:"route,omitempty" json:"route,omitempty"`
}
ExtensionTarget identifies the host node to extend.
type GraphBuilder ¶
type GraphBuilder struct {
// contains filtered or unexported fields
}
GraphBuilder produces a per-(route, principal) filtered graph by walking the merged graph from the registry and dropping nodes whose visibleWhen predicates fail. EnabledWhen is preserved as an annotation (it does not strip the node); the React shell honors it for disabled-but-visible UI states.
func NewGraphBuilder ¶
func NewGraphBuilder(reg Registry, wardens WardenRegistry) *GraphBuilder
NewGraphBuilder returns a builder bound to the given registry and warden registry.
func (*GraphBuilder) Build ¶
func (b *GraphBuilder) Build(ctx context.Context, contributor, route string, p Principal) (*GraphNode, error)
Build returns the filtered graph rooted at the given route for the given principal. Returns ErrNotFound if no contributor owns the route, or ErrPermissionDenied if the root node itself is filtered out by the principal's permissions.
func (*GraphBuilder) BuildWithParams ¶
func (b *GraphBuilder) BuildWithParams(ctx context.Context, contributor, route string, p Principal) (*GraphNode, map[string]string, error)
BuildWithParams is Build plus the route-pattern params extracted from the matched route. For exact-route matches the map is empty (non-nil); for :name-style routes it carries the placeholder values. Slice (j) added this so the transport handler can surface params in ResponseMeta.
type GraphCache ¶
type GraphCache struct {
// contains filtered or unexported fields
}
GraphCache is a small LRU+TTL cache. Bust on contributor manifest reload.
func NewGraphCache ¶
func NewGraphCache(maxEntries int, ttl time.Duration) *GraphCache
NewGraphCache creates a cache with the given max size and TTL per entry. TTL of 0 disables expiry.
func (*GraphCache) BustAll ¶
func (c *GraphCache) BustAll()
BustAll clears the cache. Call after a contributor manifest reload or shell deploy.
func (*GraphCache) Get ¶
func (c *GraphCache) Get(k GraphCacheKey) (*GraphNode, bool)
func (*GraphCache) Put ¶
func (c *GraphCache) Put(k GraphCacheKey, v *GraphNode)
type GraphCacheKey ¶
GraphCacheKey is the (route, permissionsHash, shellVersion) tuple keyed by the cache.
type GraphNode ¶
type GraphNode struct {
Route string `yaml:"route,omitempty" json:"route,omitempty"` // top-level only
Intent string `yaml:"intent" json:"intent"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Root bool `yaml:"root,omitempty" json:"root,omitempty"`
Data *DataBinding `yaml:"data,omitempty" json:"data,omitempty"`
Props map[string]any `yaml:"props,omitempty" json:"props,omitempty"`
Slots map[string][]GraphNode `yaml:"slots,omitempty" json:"slots,omitempty"`
VisibleWhen *Predicate `yaml:"visibleWhen,omitempty" json:"visibleWhen,omitempty"`
EnabledWhen *Predicate `yaml:"enabledWhen,omitempty" json:"enabledWhen,omitempty"`
Op string `yaml:"op,omitempty" json:"op,omitempty"` // for action nodes
Payload map[string]ParamSource `yaml:"payload,omitempty" json:"payload,omitempty"`
Component string `yaml:"component,omitempty" json:"component,omitempty"` // intent: custom escape hatch
Src string `yaml:"src,omitempty" json:"src,omitempty"` // intent: iframe escape hatch
Sandbox []string `yaml:"sandbox,omitempty" json:"sandbox,omitempty"`
Protocol string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
}
GraphNode is a single node in the UI graph (an intent invocation with slot fills).
type Intent ¶
type Intent struct {
Name string `yaml:"name" json:"name"`
Kind IntentKind `yaml:"kind" json:"kind"`
Version int `yaml:"version" json:"version"`
Capability Capability `yaml:"capability" json:"capability"`
Requires Predicate `yaml:"requires,omitempty" json:"requires,omitempty"`
Schema IntentSchema `yaml:"schema,omitempty" json:"schema,omitempty"`
Mode SubscriptionMode `yaml:"mode,omitempty" json:"mode,omitempty"` // subscription only
Invalidates []string `yaml:"invalidates,omitempty" json:"invalidates,omitempty"` // command only
Audit *bool `yaml:"audit,omitempty" json:"audit,omitempty"` // default true for commands
Deprecated *Deprecation `yaml:"deprecated,omitempty" json:"deprecated,omitempty"`
}
Intent declares a single named operation and its security/version metadata.
type IntentKind ¶
type IntentKind string
IntentKind is the wire-level discriminator declared on every intent. It must be consistent with the request envelope Kind at dispatch time.
const ( IntentKindGraph IntentKind = "graph" IntentKindQuery IntentKind = "query" IntentKindCommand IntentKind = "command" IntentKindSubscription IntentKind = "subscription" )
type IntentKindDef ¶
IntentKindDef declares the slots of a built-in intent kind.
type IntentSchema ¶
type IntentSchema struct {
Input map[string]any `yaml:"input,omitempty" json:"input,omitempty"`
Output any `yaml:"output,omitempty" json:"output,omitempty"`
}
IntentSchema is loose by design: contributors describe their input/output shapes; validation against this is opt-in (slice (b) wires it).
type Kind ¶
type Kind string
Kind discriminates request/response semantics on the wire. A kind is enforced against the intent's declared Capability at dispatch time.
type NavConfig ¶
type NavConfig struct {
}
NavConfig is per-route nav metadata; mirrors today's contributor.NavItem fields.
type NoopAuditEmitter ¶
type NoopAuditEmitter struct{}
NoopAuditEmitter is the disabled-audit implementation.
func (NoopAuditEmitter) Emit ¶
func (NoopAuditEmitter) Emit(_ context.Context, _ AuditRecord)
type ParamSource ¶
type ParamSource struct {
Value any `yaml:"value,omitempty" json:"value,omitempty"`
From string `yaml:"from,omitempty" json:"from,omitempty"` // route.X | parent.X | state.X | session.X
}
ParamSource describes where a parameter value comes from. Exactly one of Value/From is set; YAML uses { from: route.tenant } or a literal.
func (*ParamSource) UnmarshalYAML ¶
func (p *ParamSource) UnmarshalYAML(value *yaml.Node) error
UnmarshalYAML accepts either a scalar (treated as the From source) or a mapping with the explicit {value} or {from} form.
type Predicate ¶
type Predicate struct {
All []string `yaml:"all,omitempty" json:"all,omitempty"`
Any []string `yaml:"any,omitempty" json:"any,omitempty"`
Not []string `yaml:"not,omitempty" json:"not,omitempty"`
Warden string `yaml:"warden,omitempty" json:"warden,omitempty"`
}
Predicate is the boolean access expression: any of all/any/not, plus an optional named Warden delegate. An empty Predicate evaluates to allow.
type Principal ¶
Principal is the caller identity passed to Wardens and the predicate engine.
func PrincipalFor ¶
PrincipalFor builds a Principal from a UserInfo, copying claims for safety.
type Query ¶
type Query struct {
Intent string `yaml:"intent" json:"intent"`
Params map[string]ParamSource `yaml:"params,omitempty" json:"params,omitempty"`
Cache *QueryCache `yaml:"cache,omitempty" json:"cache,omitempty"`
}
Query is a named, reusable, cacheable data binding referenced by graph nodes.
type QueryCache ¶
type QueryCache struct {
StaleTime string `yaml:"staleTime,omitempty" json:"staleTime,omitempty"`
}
QueryCache declares per-query staleness for the client.
type Registry ¶
type Registry interface {
Register(m *ContractManifest) error
Contributor(name string) (*ContractManifest, bool)
Intent(contributor, intent string, version int) (Intent, bool)
HighestVersion(contributor, intent string) (int, bool)
All() []*ContractManifest
MergedGraph(contributor, route string) (*GraphNode, bool)
// MatchRoute is MergedGraph plus :name-style placeholder matching. On a
// match the returned map carries the extracted segment values keyed by
// placeholder name. Exact route matches return an empty (non-nil) map.
// Slice (j) added this for deep-link detail routes; MergedGraph remains
// for callers that don't care about params.
MatchRoute(contributor, route string) (*GraphNode, map[string]string, bool)
// RegisterRemote records a contributor whose handlers live in another
// service. The manifest is registered identically to a local one so the
// graph endpoint and capabilities listing work uniformly; the endpoint
// is what the dispatcher's forwarding layer reads to know where to send
// envelopes. Slice (m) added this.
RegisterRemote(m *ContractManifest, endpoint RemoteEndpoint) error
// IsRemote reports whether the named contributor was registered via
// RegisterRemote.
IsRemote(contributor string) bool
// Remote returns the upstream endpoint for a contributor previously
// registered via RegisterRemote. ok is false for local contributors.
Remote(contributor string) (RemoteEndpoint, bool)
// Unregister removes a contributor and all its intents + merged graph.
// Used by discovery loops to clean up offline remotes; safe to call
// for unknown names.
Unregister(contributor string)
}
Registry holds all registered contributor manifests and provides lookup by (contributor, intent, version) plus highest-active-version queries for negotiation. It also stores per-contributor merged graphs reflecting any cross-contributor slot extensions applied at registration time.
type RemoteEndpoint ¶
type RemoteEndpoint struct {
// BaseURL is the upstream service's root, including any path prefix
// (e.g. https://svc.internal:8443 or /proxied/svc). The forwarding
// client appends "/_forge/contract/dispatch" for envelope POSTs and
// "/_forge/contract/manifest" for manifest fetches.
BaseURL string
// APIKey, when non-empty, is sent as Authorization: Bearer <key> on
// every forwarded envelope so the upstream can authenticate the
// dashboard. Inbound user headers (Authorization, Cookie) are still
// forwarded so the upstream sees the end-user identity too — the
// API key authenticates the dashboard itself; user identity flows in
// parallel.
APIKey string
// Client overrides the http.Client used to talk to this remote.
// nil = a default client with a 10s timeout.
Client *http.Client
}
RemoteEndpoint describes how to reach a contract contributor that lives in another service. Slice (m) introduced this so the dispatcher's forwarding layer knows where to send envelopes for a contributor whose handlers are out-of-process.
type Request ¶
type Request struct {
Envelope string `json:"envelope"`
Kind Kind `json:"kind"`
Contributor string `json:"contributor"`
Intent string `json:"intent"`
IntentVersion int `json:"intentVersion,omitempty"`
Payload json.RawMessage `json:"payload,omitempty"`
Params map[string]any `json:"params,omitempty"`
Context RequestContext `json:"context"`
CSRF string `json:"csrf,omitempty"`
IdempotencyKey string `json:"idempotencyKey,omitempty"`
}
Request is the wire envelope for POST /api/dashboard/{envelope}.
type RequestContext ¶
type RequestContext struct {
Route string `json:"route,omitempty"`
CorrelationID string `json:"correlationID,omitempty"`
}
RequestContext carries route + correlation metadata. Always populated by the shell.
type Response ¶
type Response struct {
OK bool `json:"ok"`
Envelope string `json:"envelope"`
Kind Kind `json:"kind"`
Data json.RawMessage `json:"data,omitempty"`
Meta ResponseMeta `json:"meta"`
}
Response is the wire envelope for successful POST responses.
type ResponseMeta ¶
type ResponseMeta struct {
IntentVersion int `json:"intentVersion,omitempty"`
Deprecation *Deprecation `json:"deprecation,omitempty"`
CacheControl *CacheHint `json:"cacheControl,omitempty"`
Invalidates []string `json:"invalidates,omitempty"`
RouteParams map[string]string `json:"routeParams,omitempty"`
}
ResponseMeta carries cross-cutting metadata (versioning, caching, invalidation).
RouteParams is populated by graph responses for routes that contain :name placeholders (e.g. /traces/:id). The map is keyed by placeholder name and holds the matched URL value. Slice (j) introduced this so the shell can resolve `route.<name>` in payload bindings.
type SlotDef ¶
type SlotDef struct {
Accepts []string // intent names accepted in this slot
Cardinality Cardinality
Extensible bool // if true, other contributors may extend via Extends
}
SlotDef describes one slot of a parent intent kind.
type StreamEvent ¶
type StreamEvent struct {
Intent string `json:"intent"`
Mode SubscriptionMode `json:"mode"`
Payload json.RawMessage `json:"payload"`
Seq uint64 `json:"seq"`
}
StreamEvent is the SSE payload for a single subscription event.
type SubscriptionMode ¶
type SubscriptionMode string
SubscriptionMode is how the client integrates events into local state.
const ( ModeReplace SubscriptionMode = "replace" ModeAppend SubscriptionMode = "append" ModeSnapshotDelta SubscriptionMode = "snapshot+delta" )
type Warden ¶
Warden is the pluggable, data-aware authorization second pass. It runs after the YAML boolean Predicate succeeds and may inspect intent params (e.g. tenant ownership), claims, or external policy.
type WardenRegistry ¶
type WardenRegistry interface {
Register(name string, w Warden) error
Get(name string) (Warden, bool)
}
WardenRegistry maps a Warden's declared name to its implementation. Manifest validation rejects YAML that references a name not in the registry.
func NewWardenRegistry ¶
func NewWardenRegistry() WardenRegistry
NewWardenRegistry returns an empty in-memory registry.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package components provides typed, fluent builders for authoring dashboard contract graphs from Go.
|
Package components provides typed, fluent builders for authoring dashboard contract graphs from Go. |
|
Package dispatcher implements transport.Dispatcher and transport.SubscriptionSource against a function-table of registered handlers.
|
Package dispatcher implements transport.Dispatcher and transport.SubscriptionSource against a function-table of registered handlers. |
|
Package idempotency provides command deduplication for the dashboard contract: a Store interface plus an in-memory implementation.
|
Package idempotency provides command deduplication for the dashboard contract: a Store interface plus an in-memory implementation. |
|
validate.go
|
validate.go |
|
Package pilot ships the migrated dashboard contributor used to validate the contract end-to-end: extensions.list, services.list, services.detail, and the metrics.summary subscription, all wired against the existing collector and contributor registry.
|
Package pilot ships the migrated dashboard contributor used to validate the contract end-to-end: extensions.list, services.list, services.detail, and the metrics.summary subscription, all wired against the existing collector and contributor registry. |
|
Package remote implements the contract dispatcher's HTTP forwarding layer.
|
Package remote implements the contract dispatcher's HTTP forwarding layer. |
|
Package server exposes the two HTTP endpoints a non-dashboard service needs to advertise itself as a contract contributor that other dashboards can discover + dispatch into.
|
Package server exposes the two HTTP endpoints a non-dashboard service needs to advertise itself as a contract contributor that other dashboards can discover + dispatch into. |
|
capabilities.go
|
capabilities.go |