panorama

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: AGPL-3.0 Imports: 17 Imported by: 0

Documentation

Overview

Package panorama owns Atlas's design-time ArchiMate models (ADR-0189). It deliberately has no dependency on the engine, processor, WAL, or runtime state: Panorama documents are architecture source, not execution facts.

Index

Constants

View Source
const (
	// StatusResolved: the resource exists and this caller may see it.
	StatusResolved = "resolved"
	// StatusForbidden: the resource exists, but outside this caller's access. Kept
	// distinct from missing on purpose — "you may not see it" and "nothing here has
	// that id" are fixed in different places, and one is a sharing decision while
	// the other is a typo or a deleted resource. Collapsing them would send
	// somebody to correct a model that is already correct.
	//
	// The disclosure this makes is that the id resolves to something. That id is
	// already in the caller's own model, which they can export; what the scope
	// withholds is the name, and the name is what stays out.
	StatusForbidden = "forbidden"
	// StatusMissing: nothing on this server has that id.
	StatusMissing = "missing"
	// StatusUnsupported: this server cannot resolve this kind of binding yet,
	// because nothing supplies a catalog for it. Distinct from missing because
	// missing is a claim — "nothing here has that id" — and it would be false: the
	// resolver did not look. A binding to a runtime id before the node descriptor
	// exists (ADR-0189 §6, delivered by P4) is the case this is for.
	StatusUnsupported = "unsupported"
)

Binding resolution statuses.

View Source
const (
	KeyApplicationID      = "atlas.applicationId"
	KeyProcessID          = "atlas.processId"
	KeyConnectorID        = "atlas.connectorId"
	KeyJobType            = "atlas.jobType"
	KeyRuntimeID          = "atlas.runtimeId"
	KeyDeploymentTargetID = "atlas.deploymentTargetId"
	KeyReleaseID          = "atlas.releaseId"
)

The binding keys. This is an allowlist, and that is load-bearing: ADR-0189 §4 forbids credential references, tokens, passwords and secret values in a binding, and an allowlist makes that structural. atlas.credentialRef is refused because it was never permitted, not because somebody remembered to ban it.

View Source
const (
	C4Person         = "Person"
	C4SoftwareSystem = "SoftwareSystem"
	C4Container      = "Container"
)

The C4 vocabulary this projection uses.

View Source
const (
	// ContextSourceEvents is Atlas's own event log, exported to an OpenSearch index.
	// It is the only store that can speak about one process: every record carries
	// the definition key it belongs to.
	ContextSourceEvents = "events"
	// ContextSourceMetrics is a Prometheus-compatible store scraping this or another
	// Atlas node. It speaks about *nodes* and never about one process — see
	// ContextUnidentifiable.
	ContextSourceMetrics = "metrics"
)

Context sources. Each names a store outside Atlas, because an answer's worth depends on where it came from and an operator disagreeing with one needs to know which system to go and argue with.

View Source
const (
	// ContextNotConfigured: no such store is wired to this server. Nothing was
	// asked, so nothing is claimed.
	ContextNotConfigured = "not-configured"
	// ContextUnidentifiable: the store is wired and was not asked, because it cannot
	// name a thing of this kind at all. Atlas's metrics carry no per-element labels
	// by deliberate design (ADR-0142 forbids labelling by process id, instance key
	// or any other value the data can invent), so a metrics store can answer about a
	// node and never about one process. That is a property of the contract, not a
	// gap in the data, and reporting it as "nothing found" would send somebody to
	// look for a series that must not exist.
	ContextUnidentifiable = "unidentifiable"
	// ContextUnreachable: it was asked and could not be reached. Nothing is known.
	ContextUnreachable = "unreachable"
	// ContextRefused: it was reached and declined to answer — credentials, or a
	// permission on its side. Something is there and this server may not have it,
	// which is a different thing to tell an operator than "it is down".
	ContextRefused = "refused"
	// ContextEmpty: asked, answered, and holds nothing about this value in this
	// window. This is the only one of the six that is a statement about the
	// architecture rather than about the lookup.
	ContextEmpty = "empty"
	// ContextAvailable: asked, answered, and has something to show.
	ContextAvailable = "available"
)

The states one source can be in for one bound value. They are six rather than two because every one of them sends somebody somewhere different, and collapsing them into "no data" would be the same lie this whole feature is arranged against: "nobody looked" and "somebody looked and found nothing" are not the same finding.

View Source
const (
	Window1h  = "1h"
	Window6h  = "6h"
	Window24h = "24h"
	Window7d  = "7d"
)

The query windows. It is an allowlist rather than a free-form duration because a window is the bound on somebody else's cluster doing work for a page of ours: an arbitrary range is an arbitrary query, and the person who owns that cluster is not the person who typed it.

View Source
const (
	KindApplication = "application"
	KindProcess     = "process"
	// KindWorker is one configured Worker — a target and identity of a Worker Type
	// (ADR-0203). The store behind it is still the worker store and the model
	// still names it with connector="…"; those are the contracts that cannot move
	// yet, and this is new surface, so it says Worker.
	KindWorker   = "worker"
	KindDecision = "decision"
	// KindDraft is a saved diagram nobody has deployed: it exists in the modeler and
	// the engine has never been given it. Distinct from KindProcess on purpose — the
	// rest of the mesh is a picture of what this server *runs*, and a draft is the
	// one node on it that runs nothing. Distinct from KindUnresolved too: nothing is
	// missing here, the work simply has not been deployed yet.
	//
	// Drafts are off by default and collected only when a caller asks for them
	// (ADR-0211 §7): an estate typically holds several drafts per deployed process,
	// so carrying them always would spend most of the size budget on diagrams that
	// are not part of the running landscape at all.
	KindDraft = "draft"
	// KindRestricted is a resource that exists but which this caller may not see.
	// It stands in for a real node so the edge to it survives (ADR-0211 §3).
	KindRestricted = "restricted"
	// KindUnresolved is a dependency nothing on this server provides: a call target
	// with no deployment, or a worker name nobody configured. Distinct from
	// restricted on purpose — "not here" and "not yours to see" are different
	// findings, and an operator chasing a broken dependency needs to tell them apart.
	KindUnresolved = "unresolved"
	// KindTarget is a deployment target: a peer Atlas this server can promote to
	// (ADR-0189 §6). It is the only kind on this landscape whose state comes from
	// outside this process, and therefore the only one that can be *unreachable* or
	// *stale* — every other node is read from local state while the request is being
	// served, so it can neither fail to be contacted nor go out of date.
	KindTarget = "target"
)

Node kinds. The mesh keeps these namespaced by prefix in Node.ID so a renderer can route a click without parsing the kind separately.

View Source
const (
	EdgeContains = "contains"
	EdgeCalls    = "calls"
	// EdgeUses is a process depending on something that is not a process: a
	// configured worker, or a decision it delegates to.
	EdgeUses = "uses"
)

Edge kinds.

View Source
const (
	// ProvenanceDerived: Atlas has this resource and no model binds to it.
	ProvenanceDerived = "derived"
	// ProvenanceModeled: a model declares this and Atlas does not have it. This is
	// the half a drawn-only view can never show — the architecture says something
	// exists and the instance disagrees.
	ProvenanceModeled = "modeled"
	// ProvenanceBoth: Atlas has it and a model binds to it.
	ProvenanceBoth = "both"
)

Provenance says how a node is known (ADR-0211 §2). The three are the point of overlaying a model onto the mesh: without them you have two pictures and no relationship between them.

View Source
const (
	StateHealthy     = "healthy"
	StateDegraded    = "degraded"
	StateNotReady    = "not-ready"
	StateUnreachable = "unreachable"
	StateStale       = "stale"
	// StateUnbound is ADR-0189 §6's "unbound/unknown": there is no observation to
	// have, or none applies. It is the default, and it is not a finding.
	StateUnbound = "unbound"
)

Observation states (ADR-0189 §6). StateUnmodeled is deliberately absent: Atlas finding a resource no model mentions is already reported, per node, as provenance (ADR-0211 §2), and repeating it as a status would make every node of an unmodeled instance carry a finding.

View Source
const (
	SeverityOK        = "ok"
	SeverityAttention = "attention"
	SeverityCritical  = "critical"
	// SeverityUnknown is the neutral rendering, not a fourth level of badness. Most
	// nodes in a young landscape are unbound, and coloring them as a problem makes
	// the whole mesh a problem.
	SeverityUnknown = "unknown"
)

Severity classes (ADR-0211 §4).

View Source
const (
	// NotationAtlas is the landscape drawn as itself: Atlas's own kinds, no
	// projection, nothing to declare.
	NotationAtlas = "atlas"
	// NotationC4 is the C4 projection. It is not a peer notation and never an
	// authoring one — ADR-0211 §8's "projection id that is not a peer notation".
	NotationC4 = "c4-projection"
)
View Source
const (
	SourceDeployments = "deployments"
	SourceInstances   = "instances"
	SourceWorkers     = "workers"
	SourceReleases    = "releases"
	SourceNode        = "node"
	// SourceRemote is a peer Atlas that was asked. It is named apart from the local
	// sources because it is the only one that can be out of date or out of reach,
	// and a reader deciding how much to trust a row needs to know which it is.
	SourceRemote = "remote"
	// SourceNone marks a binding no source on this server can say anything about.
	// It is a source in the payload precisely so that the absence is attributable:
	// "nobody looked" and "somebody looked and found nothing" are different
	// findings, and the second one is a claim.
	SourceNone = "none"
)

Observation sources. A state is only as good as what produced it, so every observation names its source rather than presenting one anonymous verdict — an operator disagreeing with a finding needs to know where to go and look.

View Source
const (
	LayerStrategy    = "strategy"
	LayerBusiness    = "business"
	LayerApplication = "application"
	LayerTechnology  = "technology"
)

The ArchiMate layers this subset spans, in the order the palette shows them: motivation-free, top-down, the way the standard's own layer diagram reads.

View Source
const (
	AspectActive   = "active"   // who or what performs behaviour
	AspectBehavior = "behavior" // what happens
	AspectPassive  = "passive"  // what is acted upon
)

The three aspects this subset needs. ArchiMate's fourth, motivation, is not in the authoring subset at all: a Goal or a Requirement relates to everything else through a different part of the matrix, and offering the elements without those rules would be offering a palette that cannot be connected to anything.

View Source
const (
	// RefusedOutOfSubset: Atlas does not author this element or relationship type
	// yet. The document may legitimately contain it, and reading one is unaffected.
	RefusedOutOfSubset = "out-of-subset"
	// RefusedByNotation: ArchiMate does not permit this relationship between these
	// two elements. No version of Atlas will allow it.
	RefusedByNotation = "not-in-archimate"
	// RefusedSelfReference: an element related to itself. The standard permits it
	// for a few relationships; Atlas does not author it, because on a canvas it is
	// almost always a mis-drop rather than a statement.
	RefusedSelfReference = "self-reference"
)

The reasons a connection is refused. They are told apart because they send somebody to different places: one is a gap in this build, the others are facts about the notation and about the document.

View Source
const (
	// NotationArchiMate32 is the stable notation id carried by Panorama resources.
	// ArchiMate 3.2 uses the 3.1 Open Exchange schema, whose XML namespace stayed
	// at /3.0/; the notation and the transport schema are related but not identical
	// version numbers.
	NotationArchiMate32 = "archimate-3.2"

	// ExchangeNamespace is the target namespace of The Open Group ArchiMate 3.1
	// Model Exchange File Format. It is also the namespace used by Atlas's existing
	// generated ArchiMate 3.2 model under docs/architecture/model.
	ExchangeNamespace = "http://www.opengroup.org/xsd/archimate/3.0/"

	// MaxXMLBytes bounds a persisted Panorama document and every validation pass.
	// Architecture models are design-time input; they must never become an
	// unbounded parser workload merely because the API accepted a large body.
	MaxXMLBytes = 4 << 20 // 4 MiB

)
View Source
const BindingContractVersion = 1

BindingContractVersion is the version of this key set. It is a small public contract: the keys below are what another tool can rely on finding, so a change to their meaning is a version bump rather than a quiet redefinition.

View Source
const C4MappingVersion = 1

C4MappingVersion versions the mapping below. It is a small public contract: a change in what an ArchiMate type projects to is a version bump, not a quiet redefinition, because exported pictures outlive the code that made them.

View Source
const ContextContractVersion = 1

ContextContractVersion is the shape of the document below.

View Source
const DefaultWindow = Window24h

DefaultWindow is what a caller who names none gets. A day is the span in which "has it been like this" is usually asked, and it is short enough that the buckets below stay readable.

View Source
const DriftContractVersion = 1

DriftContractVersion is the shape of the document below.

View Source
const NotationC4Projection = "c4-projection"

NotationC4Projection is the projection's own id. It is deliberately not a peer of archimate-3.2 in the notation list: nothing is authored in it.

View Source
const NotationMappingVersion = 1

NotationMappingVersion identifies the tables below. §8 requires a projection's mapping to be explicit *and* versioned: a reader who saw a picture last quarter has to be able to tell whether it would be drawn the same way today. Bump it whenever a row changes meaning.

View Source
const ObservationContractVersion = 1

ObservationContractVersion is the shape of the document below. It is versioned for the same reason the binding contract is: what a consumer parses outlives the code that produced it, and a field that changes meaning without a version bump is a silent lie to whatever already reads it.

View Source
const SubsetVersion = 1

SubsetVersion is the version of the table below. It is a public contract — a palette and a rule the browser enforces — so a change to what is permitted is a version bump rather than a quiet redefinition.

Variables

View Source
var ErrShuttingDown = errors.New("panorama: server is shutting down")

ErrShuttingDown is what a FactsResolver returns when it could not take its run-loop turn because the server is closing. It is a sentinel rather than a message because the handler has to answer 503 rather than 500 for it: a document built from facts nobody gathered would report every element as unobserved, and "the server is going away" is a different thing to tell a caller than "something broke".

Functions

func AddElement

func AddElement(data []byte, add NewElement) ([]byte, string, error)

AddElement writes a new element into the model and places it on a view.

Both halves happen or neither does. An element with no shape is invisible in the editor that just created it, and a shape referencing an element that is not there is what the parser reports as a broken view — either alone is a worse document than the one we started with.

func AddRelationship

func AddRelationship(data []byte, add NewRelationship) ([]byte, string, error)

AddRelationship writes a new relationship into the model and draws it on a view.

The subset decides whether it may exist at all, through the same function the canvas asked before it let the arrow be drawn — so a connection the canvas offered is one this accepts, and a connection it refused never gets here.

func AllowedBetween

func AllowedBetween(sourceType, targetType string) []string

AllowedBetween lists the relationship types this subset will author between two element types, in menu order. It is what a connect menu offers, so that the menu only ever contains choices that will be accepted.

func BindingKeys

func BindingKeys() []string

BindingKeys lists the contract's keys in a stable order, for an API that has to tell a client what it may set.

func BucketSeconds

func BucketSeconds(window ContextWindow) int64

BucketSeconds is how wide one bucket of a window is. It divides the window into at most maxContextBuckets intervals, so a longer window gets coarser buckets rather than a longer series — the panel's width does not change with the span.

func ExportArchiMate

func ExportArchiMate(g Graph, opts ArchiMateExport) []byte

ExportArchiMate writes the graph as an ArchiMate Model Exchange document.

The graph is whatever the caller was already allowed to see: this generates from a derived graph rather than re-reading anything, so the scope filtering, the restricted placeholders and the size budget all apply exactly as they did to the picture. There is nothing here that could disclose more than the landscape did.

func SetBinding

func SetBinding(data []byte, elementID, key string, values []string) ([]byte, error)

SetBinding sets one binding key on one element to the given values, replacing whatever that key held before. No values clears it. Properties that are not this key — Atlas's other keys and anybody else's properties alike — are untouched.

func SetLayout

func SetLayout(data []byte, changes []LayoutChange) ([]byte, error)

SetLayout writes new geometry for the named view nodes and leaves every other byte of the document alone.

A change naming a node the document does not have is refused rather than ignored. The canvas can only move what it drew, so an unknown id means the two have disagreed about what the document contains — and quietly dropping it would save a layout that is not the one on screen.

func WindowSeconds

func WindowSeconds(window string) (int64, bool)

WindowSeconds reports how long a window is, and whether it is one this contract allows.

func Windows

func Windows() []string

Windows lists the allowed windows, shortest first, for an API that has to tell a client what it may ask for.

Types

type AccessResolver

type AccessResolver func(r *http.Request, applicationID string) (ApplicationAccess, error)

AccessResolver resolves application ownership on the API run loop. A resolver may read the server's application store because Service invokes it only from a loop turn; it must not call Loop.Do recursively.

type Application

type Application struct {
	ID      string
	Name    string
	CanView bool
}

Application is one Atlas process application as the mesh sees it. CanView is resolved by the server against the caller's sharing scope (ADR-0071) before the graph is derived — the mesh never decides access itself, it only honors it.

type ApplicationAccess

type ApplicationAccess struct {
	Exists    bool
	CanView   bool
	CanEdit   bool
	Protected bool
}

ApplicationAccess is the caller's resolved access to the Atlas process application that owns a Panorama model. The API server supplies this result from its existing project/application scopes; Panorama does not maintain a second ACL model.

type ArchiMateExport

type ArchiMateExport struct {
	// Instance names the Atlas the landscape was read from — a host, as the reader
	// knows it. Never a credential and never a peer's address: this is the server the
	// reader is already looking at.
	Instance string
	// GeneratedAt is when, in Unix seconds. Zero says so rather than inventing one.
	GeneratedAt int64
}

ArchiMateExport is what a generated document says about where it came from.

type Binding

type Binding struct {
	ElementID   string `json:"elementId"`
	ElementType string `json:"elementType"`
	// ElementName is what the architect called it. The mesh overlay shows it, and
	// for an element Atlas has no resource for it is the only name there is.
	ElementName string   `json:"elementName,omitempty"`
	Key         string   `json:"key"`
	Values      []string `json:"values"`
}

Binding is every id bound under one key on one element. Values is a list because the exchange format expresses many-to-many by repeating the property, and ADR-0189 §4 requires that: one ArchiMate component can be implemented by several Atlas process applications, and one process application can contribute to several components.

type BindingCandidate

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

BindingCandidate is one resource a caller may bind to: an id to store and a name to show. Nothing else — a candidate list is a picker, not a resource export.

type BindingCandidates

type BindingCandidates struct {
	Key string `json:"key"`
	// Supported is false when this server cannot resolve the kind at all. Without
	// it an empty list would read as "there are none", which is a different and
	// wrong answer.
	Supported  bool               `json:"supported"`
	Candidates []BindingCandidate `json:"candidates"`
}

BindingCandidates answers "what may I bind this key to".

type BindingSet

type BindingSet struct {
	ContractVersion int       `json:"contractVersion"`
	Bindings        []Binding `json:"bindings"`
	Problems        []Problem `json:"problems"`
}

BindingSet is what one document declares, plus every reason a declaration was refused. Problems are carried rather than returned as an error: a model with one bad binding still has good ones, and hiding them all behind a single failure would make a typo look like an empty landscape.

func ExtractBindings

func ExtractBindings(data []byte) (BindingSet, error)

ExtractBindings reads every Atlas binding an Open Exchange document declares.

It never rewrites the input. Reading a model must not be able to change it, and the byte-for-byte guarantee is what keeps unsupported-but-standard content (ADR-0189 §2) safe across a round trip.

The pass is two-phase because the schema puts propertyDefinitions near the end of the model, after the elements that reference them: a property cannot be resolved on sight, so references are collected and resolved once the document is read.

type Bucket

type Bucket struct {
	At    int64   `json:"at"`
	Value float64 `json:"value"`
}

Bucket is one interval of a measure: the moment the interval starts, in Unix seconds, and the value observed across it.

type C4Element

type C4Element struct {
	ID          string `json:"id"`
	Type        string `json:"type"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	// Parent is the enclosing element, from a composition or aggregation.
	Parent     string `json:"parent,omitempty"`
	SourceType string `json:"sourceType"`
}

C4Element is one projected element. Source is the ArchiMate identifier it came from, so a reader can always get back to the authored model.

type C4Loss

type C4Loss struct {
	ID         string `json:"id"`
	SourceType string `json:"sourceType"`
	Name       string `json:"name,omitempty"`
	Reason     string `json:"reason"`
}

C4Loss is one thing the projection could not express, and why.

type C4Projection

type C4Projection struct {
	Notation       string `json:"notation"`
	SourceNotation string `json:"sourceNotation"`
	SourceModelID  string `json:"sourceModelId"`
	SourceRevision int64  `json:"sourceRevision"`
	MappingVersion int    `json:"mappingVersion"`
	// ReadOnly is always true and is in the payload on purpose: a consumer must not
	// be able to mistake a projection for a source document.
	ReadOnly      bool             `json:"readOnly"`
	Elements      []C4Element      `json:"elements"`
	Relationships []C4Relationship `json:"relationships"`
	Dropped       []C4Loss         `json:"dropped"`
}

C4Projection is a read-only view of an ArchiMate model in C4 terms.

func ProjectToC4

func ProjectToC4(data []byte, modelID string, revision int64) (C4Projection, error)

ProjectToC4 projects an Open Exchange document into C4 terms.

type C4Relationship

type C4Relationship struct {
	ID         string `json:"id"`
	Source     string `json:"source"`
	Target     string `json:"target"`
	Name       string `json:"name,omitempty"`
	SourceType string `json:"sourceType"`
}

C4Relationship is one projected arrow. SourceType names the ArchiMate relationship it came from, because C4 has only one kind and the distinction is otherwise lost.

type Call

type Call struct {
	ElementID       string
	CalledProcessID string
	TargetKey       uint64
}

Call is one call activity, already resolved by the server to the deployment it actually reaches. TargetKey is zero when nothing on this server provides the called process. Resolution belongs to the server because a call's effective target depends on deploy-time facts and call overrides (ADR-0076), not on anything the compiled model carries.

type Catalog

type Catalog struct {
	Applications map[string]ResourceRef
	Processes    map[string]ResourceRef
	Connectors   map[string]ResourceRef
	JobTypes     map[string]ResourceRef
	Runtimes     map[string]ResourceRef
	Targets      map[string]ResourceRef
	Releases     map[string]ResourceRef
}

Catalog is what the server supplies to resolve a document's bindings: one lookup per binding kind, already filtered for the caller. Each key resolves against its own map — looking a process id up among applications would report every binding as missing, which reads as a broken model rather than as a broken resolver.

type CatalogResolver

type CatalogResolver func(r *http.Request) (Catalog, error)

CatalogResolver supplies the Atlas resources a document's bindings resolve against, already filtered for this caller (ADR-0189 §4). Like AccessResolver it runs inside a run-loop turn, so it may read the server's stores directly and must not call Loop.Do recursively.

type Clock

type Clock func() time.Time

Clock supplies deterministic timestamps in tests.

type ContextDocument

type ContextDocument struct {
	ContractVersion int             `json:"contractVersion"`
	ElementID       string          `json:"elementId"`
	ObservedAt      int64           `json:"observedAt"`
	Window          ContextWindow   `json:"window"`
	Results         []ContextResult `json:"results"`
	// Truncated reports that results were dropped to stay inside the bound, so a
	// short answer is not read as a complete one.
	Truncated bool           `json:"truncated"`
	Limits    []ContextLimit `json:"limits"`
}

ContextDocument is what a caller asking "has it been like this" gets, for one element.

It is scoped to one element rather than to a model on purpose. Every bound value costs a query against somebody else's cluster, and a model-wide answer would multiply that by the whole landscape for a panel that shows one element at a time.

func AssembleContext

func AssembleContext(elementID string, window ContextWindow, results []ContextResult, now int64) ContextDocument

AssembleContext builds the document from what the sources returned.

It bounds and orders; it never invents. A result missing from the input is missing from the output, because a placeholder this function made up would be indistinguishable from an answer a source actually gave.

type ContextLimit

type ContextLimit struct {
	Limit  string `json:"limit"`
	Reason string `json:"reason"`
}

ContextLimit is one thing this surface cannot do, with the reason.

type ContextQuery

type ContextQuery struct {
	Key    string
	Value  string
	Window ContextWindow
}

ContextQuery is one lookup the server is asked to perform: a bound value, and the window to ask about.

func QueriesFor

func QueriesFor(set BindingSet, elementID string, window ContextWindow) []ContextQuery

QueriesFor lists the lookups one element's bindings imply, in a stable order.

A binding whose value no source can identify still produces a query: the answer *unidentifiable* is one this document owes the reader, and dropping the binding here would make it indistinguishable from an element that binds nothing.

type ContextResolver

type ContextResolver func(r *http.Request, queries []ContextQuery) ([]ContextResult, error)

ContextResolver asks whichever historical stores are wired about one element's bound values, and returns one result per (source, value) pair it considered.

Like FactsResolver it is called *off* the run loop and takes its own turns for the parts that need one: every adapter here talks to a store outside this process, and holding the single writer across that is the one thing every other request is waiting for it not to do (invariant I3).

It returns a result for every pair it considered, including the ones it could not answer. A resolver that returned only its successes would make "no store is wired" and "the store holds nothing" the same answer, which is exactly the conflation the six states exist to prevent.

type ContextResult

type ContextResult struct {
	Source string `json:"source"`
	Key    string `json:"key"`
	Value  string `json:"value"`
	State  string `json:"state"`
	// Reason is why the state is what it is, in the source's own words. It is
	// required for every state except available: a source that could not answer owes
	// the reader a sentence, and one that could does not need to explain itself.
	Reason   string            `json:"reason,omitempty"`
	Measures []Measure         `json:"measures,omitempty"`
	Detail   map[string]string `json:"detail,omitempty"`
}

ContextResult is one source's answer about one bound value.

type ContextWindow

type ContextWindow struct {
	Window string `json:"window"`
	From   int64  `json:"from"`
	To     int64  `json:"to"`
}

ContextWindow is the span a result covers, in Unix seconds.

func NewContextWindow

func NewContextWindow(window string, now int64) (ContextWindow, bool)

NewContextWindow resolves a requested window name against the allowlist and anchors it at now.

type Decision

type Decision struct {
	ID      string
	Name    string
	CanView bool
}

Decision is one local DMN decision a business-rule task delegates to. A remote decision, evaluated by a worker rather than in this engine, is deliberately not one of these: it arrives as a WorkerUse instead, which is where its dependency actually points.

type Draft

type Draft struct {
	ProcessID     string
	Name          string
	ApplicationID string
	CanView       bool
}

Draft is one saved-but-not-deployed diagram. It carries far less than a Process because there is far less to know: a draft has no deployment key, no version, no instances and no observation, and inventing any of them would put a runtime claim on a thing that has never run.

A draft whose process id is already deployed is not one of these. The server leaves it out, because that draft and the deployed process are the same work seen at two altitudes — drawing both would put a twin beside every process on the canvas and say nothing true about either.

type DriftDocument

type DriftDocument struct {
	ContractVersion int          `json:"contractVersion"`
	Entries         []DriftEntry `json:"entries"`
	// Since is the moment from which this document can speak, or zero when this
	// model has never been read. It is the honest bound on every question asked of
	// it: nothing before it can be answered, whether or not anything happened.
	//
	// While nothing has been dropped that is when the journal *started watching*,
	// not the oldest entry it holds — a quiet hour is a real answer, and dating the
	// document from its first entry would throw that hour away. Once entries have
	// been dropped it is the oldest retained entry instead, because everything
	// before that one genuinely is gone.
	Since int64 `json:"since"`
	// Truncated reports that entries were dropped to stay inside the bound, so an
	// empty stretch is not read as a quiet one.
	Truncated bool         `json:"truncated"`
	Limits    []DriftLimit `json:"limits"`
}

DriftDocument is what a caller asking "what changed" gets.

type DriftEntry

type DriftEntry struct {
	ElementID string `json:"elementId"`
	Key       string `json:"key"`
	Value     string `json:"value"`
	// From is the state this value was last seen in, and To the state it is in
	// now. Both travel because the direction is the finding: healthy → degraded and
	// degraded → healthy are the same transition seen from opposite sides, and one
	// of them is somebody's incident and the other is somebody's fix.
	From string `json:"from"`
	To   string `json:"to"`
	// Reason is the new state's own sentence, kept so an entry read a week later
	// still says what happened rather than only that something did.
	Reason string `json:"reason,omitempty"`
	At     int64  `json:"at"`
}

DriftEntry is one recorded change of one bound value's state.

type DriftLimit

type DriftLimit struct {
	Limit  string `json:"limit"`
	Reason string `json:"reason"`
}

DriftLimit is one thing this journal cannot see, with the reason.

type Edge

type Edge struct {
	From string `json:"from"`
	To   string `json:"to"`
	Kind string `json:"kind"`
}

Edge is one directed relationship between two nodes.

type ElementKind

type ElementKind struct {
	// Type is the xsi:type written into the document — the standard's own name,
	// because the document is an interchange format and not Atlas's private store.
	Type string `json:"type"`
	// Label is what a person reads in the palette; Layer groups it there.
	Label string `json:"label"`
	Layer string `json:"layer"`
	// Aspect is the element's role in the standard's structure/behaviour split, and
	// it is what most of the relationship rules below are actually about. Carrying
	// it makes those rules readable as the sentences the specification states,
	// rather than as a list of type pairs somebody has to trust.
	Aspect string `json:"aspect"`
}

ElementKind is one authorable element type.

func AuthorableElements

func AuthorableElements() []ElementKind

AuthorableElements returns the element subset, in palette order.

type Fact

type Fact struct {
	Source string
	State  string
	Reason string
	// Detail is a small, flat set of specifics — a version, a count — for a reader
	// who wants the number behind the sentence. It is bounded on the way out
	// (see boundDetail): this document is fetched per model view, and an unbounded
	// detail object is how one becomes too big to render.
	Detail map[string]string
}

Fact is one runtime fact the server observed about one Atlas resource. The server builds these; this package only projects them onto the elements that bind to them.

type Facts

type Facts struct {
	Applications map[string]Fact
	Processes    map[string]Fact
	Connectors   map[string]Fact
	JobTypes     map[string]Fact
	Runtimes     map[string]Fact
	Targets      map[string]Fact
	Releases     map[string]Fact
}

Facts is what the server supplies, one lookup per binding kind, already filtered for the caller. As with Catalog, a nil map and an empty map are different answers: nil means nothing on this server observes that kind at all, empty means it was looked at and holds none.

type FactsResolver

type FactsResolver func(r *http.Request) (Facts, error)

FactsResolver supplies the runtime facts an observation document is projected from, already filtered for this caller.

Unlike CatalogResolver it is called *off* the run loop, and takes its own loop turns for the parts that need one. That inversion is deliberate: gathering these facts includes asking peer servers (ADR-0189 §6), and holding the single writer for the duration of a network call is the one thing every other request on this server is waiting for it not to do (invariant I3). A resolver whose on-loop half could not run returns ErrShuttingDown.

type Graph

type Graph struct {
	Nodes []Node `json:"nodes"`
	Edges []Edge `json:"edges"`
	// Restricted is how many placeholder nodes stand in for resources this caller
	// may not see. The legend states it, so a filtered picture says that it is
	// filtered instead of looking complete.
	Restricted int `json:"restricted"`
	// Clustered reports that the graph exceeded its size budget and collapsed to
	// applications.
	Clustered bool `json:"clustered"`
	// The desired-versus-observed comparison, when a model was overlaid.
	// Modeled counts what a model declares and Atlas does not have; Unmodeled counts
	// what Atlas has and no model mentions.
	Modeled   int `json:"modeled"`
	Unmodeled int `json:"unmodeled"`
	// OutOfScope counts bindings to kinds this picture does not draw — releases,
	// deployment targets, runtimes. They are neither matched nor absent, and the
	// count keeps that visible instead of silently dropping them.
	OutOfScope int `json:"outOfScope"`
	// Status is the severity summary and, as importantly, the declaration of which
	// observation states this build cannot produce at all (ADR-0211 §4).
	Status Status `json:"status"`
	// ObservedAt is when this picture was derived, in Unix seconds, and zero when
	// the caller supplied no clock.
	//
	// It is on the payload rather than left to the reader's own clock because the
	// picture outlives the tab it was drawn in: ADR-0211 §10 requires an exported
	// landscape to carry its observation time *into* the artifact, and a time the
	// browser invented would date the export rather than the facts in it. The two
	// differ by however long the file sat unsaved, and by all of history once
	// somebody re-exports a stale tab.
	ObservedAt int64 `json:"observedAt,omitempty"`
}

Graph is one derived mesh.

func DeriveGraph

func DeriveGraph(land Landscape, opts Options) Graph

DeriveGraph computes the mesh for one caller's landscape.

type IDGenerator

type IDGenerator func() (string, error)

IDGenerator mints an opaque Panorama model id off the run loop.

type IncidentSite

type IncidentSite struct {
	// ElementID is the BPMN element id, which is what an operator searches the
	// diagram for.
	ElementID string `json:"elementId"`
	// ElementType is the BPMN type ("ServiceTask", "CallActivity", …), so the id
	// reads as a thing rather than as a string.
	ElementType string `json:"elementType,omitempty"`
	// Count is how many incidents are parked on this element.
	Count int `json:"count"`
	// Message is the first one seen here, not a summary of all of them: several
	// incidents on one element usually share a cause, and inventing a combined
	// sentence would be writing a message nobody produced. Operations has the rest.
	Message string `json:"message,omitempty"`
}

IncidentSite is one element of a process with unresolved incidents on it.

"Three tokens are parked" tells somebody there is a problem; "three tokens are parked on the service task charge-card, and the last one said 502 Bad Gateway" tells them where to go. That is the difference between a status view somebody glances at and one they act on, and it costs the same read either way — the engine already knows which element each incident is stuck on, because that is how it resolves one.

The element is named by its BPMN id and type rather than by a label. Only user tasks carry a human title in a compiled process, so a label would be present for some elements and absent for others — and an identifier that is sometimes there is worse than one that is always there. It is also what the Operations view shows, so the two name the same thing the same way.

type Journal

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

Journal records what changed between one observation document and the next.

It carries its own lock rather than living on the run loop: observations are computed off the loop (a document can wait on peer servers), so this is written from request goroutines and putting it behind the single writer would mean holding that writer across a network call.

func NewJournal

func NewJournal() *Journal

func (*Journal) Document

func (j *Journal) Document(modelID string) DriftDocument

Document returns one model's journal, newest first — which is the order somebody asking "what changed" reads in.

func (*Journal) Record

func (j *Journal) Record(modelID string, doc ObservationDocument) ObservationDocument

Record folds one observation document into the journal and returns the document enriched with what it now knows about each value's last change.

The first read of a model records nothing. That is deliberate: everything would otherwise arrive as a transition from nothing, and a journal whose first page is always a full inventory teaches a reader to skip the first page.

type Landscape

type Landscape struct {
	Applications []Application
	Processes    []Process
	// Drafts are the saved diagrams nobody has deployed, and are empty unless the
	// caller asked for them — see [Draft].
	Drafts    []Draft
	Workers   []Worker
	Decisions []Decision
	// Targets are the peers this server can promote to, and what asking them
	// produced. Filled in two halves: the collector names them on the run loop, and
	// [ReachOut] supplies each one's state off it.
	Targets []Target
	// PartialStatus reports that the server stopped counting parked work before it
	// had seen all of it. It travels with the landscape because only the collector
	// knows it, and it must reach the payload: without it a process the scan never
	// reached would be published as healthy on no evidence at all.
	PartialStatus bool
}

Landscape is everything the mesh derives from, already filtered for this caller.

type LandscapeCollector

type LandscapeCollector func(r *http.Request) (Landscape, ReachOut, error)

LandscapeCollector reads the server's resources and returns them filtered for this caller: every Application and Process it yields carries the CanView the caller's sharing scope resolves to (ADR-0071). It runs on the API run loop, so it may read the project and deployment registries directly and must not call Loop.Do recursively.

The mesh takes it as a function rather than reaching for the stores itself. That is ADR-0147's rule — a per-area service holds a loop and takes every other dependency explicitly — and it is also what keeps this package free of the server object it would otherwise have to import. It also hands back the part of the landscape that cannot be read from local state — see ReachOut — or nil when there is none.

type LayoutChange

type LayoutChange struct {
	// NodeID is the view node's `identifier`, not the element it references. A view
	// may place the same element more than once, and each placement moves on its own.
	NodeID string `json:"nodeId"`
	X      int    `json:"x"`
	Y      int    `json:"y"`
	W      int    `json:"w"`
	H      int    `json:"h"`
}

LayoutChange is one view node's new geometry, in the document's own units.

type Measure

type Measure struct {
	// Name is the stable identifier a client may key on; Label is what a person
	// reads. Both travel because one of them is a contract and the other is prose,
	// and using either for both jobs makes it impossible to change the prose.
	Name  string `json:"name"`
	Label string `json:"label"`
	// Total is the measure across the whole window. It is carried rather than left
	// to be summed from the buckets, because for a gauge the sum of the buckets is
	// not the total and a client cannot tell which kind it has.
	Total   float64  `json:"total"`
	Buckets []Bucket `json:"buckets"`
}

Measure is one named series over the window.

type Mesh

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

func NewMesh

func NewMesh(loop *runloop.Loop, collect LandscapeCollector, overlays OverlayCollector,
	maxNodes int, now Clock) *Mesh

NewMesh builds the mesh service. maxNodes is the size budget; zero is unlimited. overlays may be nil, in which case no desired-versus-observed comparison is made. now stamps each answer with the moment its landscape was read; a nil clock leaves the stamp off rather than inventing one.

func (*Mesh) HandleArchiMate

func (m *Mesh) HandleArchiMate(w http.ResponseWriter, r *http.Request)

HandleArchiMate serves the landscape as an ArchiMate Open Exchange document.

It derives exactly what HandleGraph derives and writes that, so the file and the picture are the same landscape seen twice rather than two answers to one question. Whatever the caller may not see is missing from both, identically, and the document says how much of it there was.

func (*Mesh) HandleGraph

func (m *Mesh) HandleGraph(w http.ResponseWriter, r *http.Request)

HandleGraph serves the whole-instance mesh for the calling principal.

func (*Mesh) HandleNotations

func (m *Mesh) HandleNotations(w http.ResponseWriter, _ *http.Request)

HandleNotations serves the vocabularies the landscape can be drawn in, with each one's mapping and what it drops (ADR-0211 §8).

It is served rather than duplicated in the browser for the reason ADR-0189's connection subset already gives: three things read this table — the picture's labels, the stamp on its image export, and the ArchiMate document generated from it — and a copy in the view would eventually have the picture calling a node one thing while the file called it another.

No authorization beyond the route's own: this is a description of a mapping, not of anybody's resources.

type Model

type Model struct {
	ID            string `json:"id"`
	ApplicationID string `json:"applicationId"`
	Name          string `json:"name"`
	Notation      string `json:"notation"`
	Revision      int64  `json:"revision"`
	XML           string `json:"xml"`
	CreatedAt     int64  `json:"createdAt"`
	CreatedBy     string `json:"createdBy,omitempty"`
	UpdatedAt     int64  `json:"updatedAt"`
	UpdatedBy     string `json:"updatedBy,omitempty"`
}

Model is one application-owned Panorama architecture document. XML remains the canonical source; the surrounding fields provide Atlas ownership, optimistic concurrency, and cheap listings without parsing every document.

type ModelElement

type ModelElement struct {
	ElementID   string
	ElementType string
	Name        string
	Key         string
	Values      []string
}

ModelElement is one bound ArchiMate element: what the architect called it, and which Atlas ids its binding names (ADR-0189 §4).

type NewElement

type NewElement struct {
	// Type is an ArchiMate element type from the authoring subset. Anything else is
	// refused: this writer will not create what the canvas cannot draw rules for.
	Type string `json:"type"`
	Name string `json:"name"`
	// ViewID is the view the new shape is placed on, and the geometry is where.
	ViewID string `json:"viewId"`
	X      int    `json:"x"`
	Y      int    `json:"y"`
	W      int    `json:"w"`
	H      int    `json:"h"`
}

NewElement is one element to add, and where it goes on a view.

type NewRelationship

type NewRelationship struct {
	Type   string `json:"type"`
	Source string `json:"source"`
	Target string `json:"target"`
	ViewID string `json:"viewId"`
}

NewRelationship is one relationship to add, and the view to draw it on.

type Node

type Node struct {
	ID         string `json:"id"`
	Kind       string `json:"kind"`
	Name       string `json:"name,omitempty"`
	Provenance string `json:"provenance"`
	// Application is the owning application's node id, for grouping. Empty on
	// application, restricted, and unresolved nodes.
	Application string `json:"application,omitempty"`
	// ProcessID and Version identify a process node well enough to navigate to the
	// Operations view (L2) without a second lookup.
	ProcessID string `json:"processId,omitempty"`
	Version   int32  `json:"version,omitempty"`
	// WorkerType is a worker node's Worker Type ("rest", "mail", …). Never its
	// endpoint and never its credential reference — see [Worker].
	WorkerType string `json:"workerType,omitempty"`
	// The ArchiMate element bound to this node, when a model binds one. ModelName is
	// carried beside Name rather than replacing it: the architect's name and the
	// Atlas name are allowed to differ, and the difference is informative.
	ModelElementID   string `json:"modelElementId,omitempty"`
	ModelElementType string `json:"modelElementType,omitempty"`
	ModelName        string `json:"modelName,omitempty"`
	// State is the observation state behind Severity (ADR-0189 §6), kept beside it
	// because the three classes are a reading aid and never a replacement: an
	// operator acting on a finding needs the state, not the color.
	State    string `json:"state"`
	Severity string `json:"severity"`
	// Reason is the sentence behind this node's severity, in the words of whatever
	// observed it. Empty on a node with nothing to report.
	Reason string `json:"reason,omitempty"`
	// SeverityFrom names the descendant a node inherited its severity from, and is
	// empty when the severity is the node's own. ADR-0211 §4 requires it: a red
	// parent that cannot say which child is red is not actionable.
	SeverityFrom string `json:"severityFrom,omitempty"`
	// Incidents is how many unresolved incidents the engine holds against this node.
	// Only a process node can carry one — an incident belongs to a token, and only a
	// process has tokens — so it is absent everywhere else rather than zero, because
	// "no incidents" and "cannot have incidents" are different facts.
	//
	// It is a count, not a severity: the state above already says what class this
	// node is in, and this says how much of it there is. A node with a count is
	// always in a state that reports one, so the two can never disagree.
	Incidents int `json:"incidents,omitempty"`
	// Sites are where in the process that work is parked (see [IncidentSite]).
	//
	// Only a process node carries them, and a collapsed application deliberately
	// does not: an element id without the process it belongs to is not something
	// anybody can act on, and a list of them from six different processes would read
	// as one broken diagram. The collapsed node keeps the summed count, which is the
	// part that survives losing the context.
	Sites []IncidentSite `json:"sites,omitempty"`
	// Children is how many nodes a collapsed application stands for. Set only when
	// the graph is clustered.
	Children int `json:"children,omitempty"`
	// Runtime is the engine's instance tally for this node (see [Runtime]).
	//
	// A process node carries its own. A *collapsed* application carries the sum of
	// the processes it stands for, for the same reason it carries their summed
	// incident count: it is standing in for them, and reporting one child's number
	// against the whole would understate it. An uncollapsed application carries
	// none — its processes are on screen with their own — and no other kind can have
	// instances at all, which is why this is absent rather than zero.
	Runtime *Runtime `json:"runtime,omitempty"`
}

Node is one vertex. A restricted node carries no Name, no process id, and no application: its kind is the whole of what it discloses.

type Notation

type Notation struct {
	ID             string                  `json:"id"`
	Label          string                  `json:"label"`
	Short          string                  `json:"short"`
	Projection     bool                    `json:"projection"`
	MappingVersion int                     `json:"mappingVersion"`
	Types          map[string]NotationType `json:"types"`
	Loss           []string                `json:"loss"`
}

Notation is one vocabulary the landscape can be drawn in.

Types maps a mesh node kind to what this notation calls it. A kind that is *absent* from the map has no counterpart, and that absence is deliberate: the node keeps its derived shape and the loss list names it. Inventing a row would be the silent drop §8's theme ban exists to prevent.

func NotationByID

func NotationByID(id string) (Notation, bool)

NotationByID resolves a notation id. An unknown id is a stale saved view or a hand-edited URL, and the derived vocabulary is the answer that cannot mislead.

func Notations

func Notations() []Notation

Notations returns every vocabulary the landscape can be drawn in, in the order the picker offers them. The slice is copied: a caller must not be able to edit the mapping the next request is answered from.

type NotationType

type NotationType struct {
	Name string `json:"name"`
	Type string `json:"type,omitempty"`
}

NotationType is what one notation calls one mesh kind.

Two fields because the two readers need different things from the same row. Name is what a person is shown — "Application Component", spaced the way the standard prints it — and Type is the notation's own machine token, which for ArchiMate is the xsi:type an exchange document must carry and for C4 does not exist, because C4 has no interchange format here. One row, so a picture and a file can never call the same node two different things.

type Observation

type Observation struct {
	ElementID   string `json:"elementId"`
	ElementType string `json:"elementType"`
	Key         string `json:"key"`
	Value       string `json:"value"`

	Source string `json:"source"`
	// State is one of ADR-0189 §6's seven; Severity is ADR-0211 §4's three-class
	// reading of it. Both travel: the class is what makes a hundred elements
	// legible at a glance, and the state is what somebody acts on.
	State    string `json:"state"`
	Severity string `json:"severity"`
	Reason   string `json:"reason,omitempty"`
	// ObservedAt is when this was read, in Unix seconds. It is on every observation
	// rather than only on the document, because the next slice resolves some of
	// them from remote nodes and those will not share one instant.
	ObservedAt int64             `json:"observedAt"`
	Detail     map[string]string `json:"detail,omitempty"`
	// ChangedAt and PreviousState are what the drift journal knows about this
	// value's last transition (ADR-0189 P5). They are on the observation rather
	// than only in the journal because "degraded" and "degraded since nine this
	// morning" are different findings, and the second is the one somebody acts on.
	// Both are absent when nothing has been seen to change — which is not the same
	// as nothing having changed, and is why the journal publishes its limits.
	ChangedAt     int64  `json:"changedAt,omitempty"`
	PreviousState string `json:"previousState,omitempty"`
}

Observation is one bound value with what the server currently sees of it.

type ObservationDocument

type ObservationDocument struct {
	ContractVersion int `json:"contractVersion"`
	// ObservedAt is when this document was computed. Every observation in it was
	// read while serving this request — nothing here is cached — which is what
	// makes one timestamp meaningful for the whole document today.
	ObservedAt   int64              `json:"observedAt"`
	Observations []Observation      `json:"observations"`
	Summary      ObservationSummary `json:"summary"`
	// Unavailable names the observation states this document cannot produce at all.
	// It is empty now that peers are asked: every one of ADR-0189 §6's states is
	// reachable here. The field stays, because an empty list is a claim somebody
	// can check — and because the landscape mesh, which draws no deployment
	// targets, still has two it cannot produce and publishes them in the same shape.
	Unavailable []UnavailableState `json:"unavailable"`
	// Problems are the extractor's, carried through exactly as binding resolution
	// carries them: a declaration that was refused is as much a finding as one that
	// resolved.
	Problems []Problem `json:"problems"`
}

ObservationDocument is what a caller asking "what is this model doing" gets.

func Observe

func Observe(set BindingSet, facts Facts, observedAt int64) ObservationDocument

Observe projects the server's facts onto the elements that bind to them.

It never drops a bound value. A binding nothing can say anything about becomes an *unbound* observation naming why, not an absent one — the same rule binding resolution follows, and for the same reason: a model whose elements quietly vanished from the live view looks like a model with nothing wrong.

type ObservationSummary

type ObservationSummary struct {
	OK        int `json:"ok"`
	Attention int `json:"attention"`
	Critical  int `json:"critical"`
	Unknown   int `json:"unknown"`
}

ObservationSummary counts the document by severity class, so a listing can say "3 need attention" without walking every element.

type Options

type Options struct {
	// Overlays are the Panorama models compared against this landscape. Empty
	// leaves the mesh exactly as the derivation alone made it.
	Overlays []Overlay
	// MaxNodes is the size budget (ADR-0211 §7). Zero means unlimited. Over budget
	// the graph collapses to applications and says so, rather than returning a graph
	// the browser cannot lay out — or, worse, a truncated one that looks complete.
	MaxNodes int
	// ObservedAt is when the landscape this graph derives from was read, in Unix
	// seconds. Zero omits it, which is what a caller with no clock gets — and is
	// deliberately not "now": a derivation that invented a timestamp would be
	// claiming a freshness nobody measured.
	ObservedAt int64
}

Options tunes one derivation.

type Overlay

type Overlay struct {
	ModelID   string
	ModelName string
	Elements  []ModelElement
}

Overlay is what one Panorama model contributes to the mesh.

type OverlayCollector

type OverlayCollector func(r *http.Request) ([]Overlay, error)

Mesh serves Panorama's derived landscape altitude (ADR-0211). It is a separate service from Service deliberately: Service owns stored ArchiMate documents, Mesh owns a projection that is never stored, and conflating declared intent with derived fact is the thing both records exist to prevent. OverlayCollector supplies the Panorama models compared against the landscape, already filtered for this caller. Like LandscapeCollector it runs on the API run loop and must not call Loop.Do recursively. Optional: a nil collector leaves the mesh exactly as the derivation alone makes it.

type Problem

type Problem struct {
	Severity string `json:"severity"`
	Message  string `json:"message"`
}

Problem is one precise reason an Open Exchange document was rejected.

type Process

type Process struct {
	Key           uint64
	ProcessID     string
	Name          string
	Version       int32
	ApplicationID string
	CanView       bool
	Calls         []Call
	Workers       []WorkerUse
	Decisions     []string
	// State is the observation state (ADR-0189 §6) the server read for this
	// process, and Reason is the sentence behind it. Empty means unbound: no
	// observation applies, which is not a finding.
	State  string
	Reason string
	// Incidents is how many unresolved incidents the engine holds against this
	// definition. It is the count behind Reason rather than a second opinion about
	// it, carried as a number so a reader can sort by it and a picture can show
	// which of two degraded processes is the worse one.
	Incidents int
	// Sites are the places in this process where that work is parked.
	Sites []IncidentSite
	// Runtime is what the engine has recorded about this definition's instances:
	// how many are live, how many have finished, and when it last did anything. It
	// is nil where the collector could not read it, which is not the same fact as
	// zero — a process nothing has ever run and a counter that failed to read are
	// different things, and only one of them is worth saying out loud.
	Runtime *Runtime
}

Process is one deployed process, with the call activities it makes.

type ReachOut

type ReachOut func(ctx context.Context, land *Landscape)

ReachOut completes a landscape with what only a call outside this process can tell it: whether the peers this server can promote to are answering.

It is a closure the collector hands back rather than a second collector, and that shape is the whole point. Whatever it needs from the run loop — the target list, the credential each one presents — it captured while it was *on* the loop; what it does with them happens off it. A remote call must never hold the single writer (I3), and a landscape view that could is a landscape view that stops every other design-time request while somebody's peer times out.

It never fails. A peer that cannot be reached is a finding about that peer, not an error about the landscape: one server rebooting must not blank a picture of four hundred nodes.

type Refusal

type Refusal struct {
	Reason  string `json:"reason"`
	Message string `json:"message"`
}

Refusal is why a connection may not be drawn, in a form a canvas can show.

func MayConnect

func MayConnect(relationship, sourceType, targetType string) (bool, *Refusal)

MayConnect reports whether this subset will author a relationship of this type between these two element types, and why not when it will not.

Both the canvas and the write path call it, which is the point: a rule that lives in one place cannot disagree with itself.

func MayConnectElements

func MayConnectElements(relationship, sourceID, sourceType, targetID, targetType string) (bool, *Refusal)

MayConnectElements is MayConnect with the two concrete elements, which is what a canvas actually has. It adds the one rule that is about identity rather than type: nothing is related to itself.

type RelationshipKind

type RelationshipKind struct {
	Type  string `json:"type"`
	Label string `json:"label"`
	// Rule is the sentence from the specification this implements, in plain words.
	// It travels to the browser so a refusal can say *why* rather than only that it
	// was refused: "you cannot draw that" teaches nobody the notation.
	Rule string `json:"rule"`
}

RelationshipKind is one authorable relationship type.

func DrawableRelationships

func DrawableRelationships() []RelationshipKind

DrawableRelationships returns the relationship subset, in menu order.

type Resolution

type Resolution struct {
	ContractVersion int               `json:"contractVersion"`
	Bindings        []ResolvedBinding `json:"bindings"`
	// Unresolved counts values that are forbidden or missing. It is the number a
	// listing shows as a badge: a model whose bindings no longer resolve is drifting
	// from the instance it describes, and that is worth seeing without opening it.
	Unresolved int `json:"unresolved"`
	// Problems are the extractor's, carried through: a caller needs to hear about
	// declarations that were refused as much as about the ones that stood.
	Problems []Problem `json:"problems"`
}

Resolution is what a caller asking "what is bound in this model" gets.

func ResolveBindings

func ResolveBindings(set BindingSet, catalog Catalog) Resolution

ResolveBindings resolves every value in a set against the catalog.

It never drops a value. ADR-0189 §4 requires a missing or inaccessible resource to stay an explicit unresolved binding: removing it would make a broken binding look like an absent one, and the model would then look correct.

type ResolvedBinding

type ResolvedBinding struct {
	ElementID   string          `json:"elementId"`
	ElementType string          `json:"elementType"`
	Key         string          `json:"key"`
	Values      []ResolvedValue `json:"values"`
}

ResolvedBinding is one key on one element, with every value it binds.

type ResolvedValue

type ResolvedValue struct {
	Value  string `json:"value"`
	Status string `json:"status"`
	Name   string `json:"name,omitempty"`
}

ResolvedValue is one bound id with what the server could say about it. Name is present only when the caller may see the resource.

type ResourceRef

type ResourceRef struct {
	ID      string
	Name    string
	CanView bool
}

ResourceRef is one Atlas resource as the resolver sees it. CanView is decided by the server against the caller's sharing scope before resolution runs; this package honors that decision and never makes one.

type Runtime

type Runtime struct {
	// Running is how many instances of this definition are live right now.
	Running int `json:"running"`
	// Finished is how many have completed or been terminated. It never decreases —
	// there is no un-finishing — so it is a lifetime total rather than a rate.
	Finished int `json:"finished"`
	// LastActivity is the Unix-nano timestamp of the most recent lifecycle event of
	// any of its instances, or zero for a definition nothing has ever run. Zero is
	// meaningful here in a way it is not for the counts: no activity ever is exactly
	// what a definition nobody has started reports.
	LastActivity int64 `json:"lastActivity,omitempty"`
}

Runtime is a process definition's instance tally (ADR-0083's summary columns, on the Starmap).

Every field is an O(1) point read of a counter the engine already maintains, which is why this can be on every node of a four-hundred-node picture: the alternative — counting instances per definition — is one scan each, and on an instance with a few hundred processes that is the difference between a view and an outage. It is the same argument the parked-work tally already makes for being collected once.

type Service

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

Service serves the Panorama model-library HTTP area (ADR-0189). Its only mutable dependency is Store, and every read and write of that store goes through loop, preserving the design-time single-writer boundary.

func New

func New(loop *runloop.Loop, store *Store, access AccessResolver, newID IDGenerator, now Clock,
	catalog CatalogResolver, facts FactsResolver) *Service

New builds a Panorama service over a run-loop-owned store.

func (*Service) CountForApplicationOnLoop

func (s *Service) CountForApplicationOnLoop(applicationID string) (int, error)

CountForApplicationOnLoop counts models owned by one application. It is a composition-root hook for application summaries and deletion guards; callers must already be executing on the Service's run loop, as the name makes explicit, so it deliberately does not call Loop.Do recursively.

func (*Service) CountsByApplicationOnLoop

func (s *Service) CountsByApplicationOnLoop() (map[string]int, error)

CountsByApplicationOnLoop returns the artifact-count contribution Panorama makes to every application. It has the same run-loop precondition as CountForApplicationOnLoop and performs one store scan for a whole listing.

func (*Service) HandleAddElement

func (s *Service) HandleAddElement(w http.ResponseWriter, r *http.Request)

HandleAddElement creates an element and places it on a view.

func (*Service) HandleAddRelationship

func (s *Service) HandleAddRelationship(w http.ResponseWriter, r *http.Request)

HandleAddRelationship creates a relationship and draws it on a view.

The subset refuses what ArchiMate does not permit, in the matrix's own words, so a caller that went around the canvas gets the same answer the canvas would have given — and learns the rule rather than only that it was refused.

func (*Service) HandleBindingCandidates

func (s *Service) HandleBindingCandidates(w http.ResponseWriter, r *http.Request)

HandleBindingCandidates lists the resources this caller may bind one key to.

ADR-0189 §4 asks the editor to let an authorized user select only resources they may see; this is that list. It is derived from the same catalog resolution uses, so a picker cannot offer something resolution would then call forbidden.

func (*Service) HandleBindings

func (s *Service) HandleBindings(w http.ResponseWriter, r *http.Request)

HandleBindings resolves every Atlas binding a model declares.

func (*Service) HandleC4

func (s *Service) HandleC4(w http.ResponseWriter, r *http.Request)

HandleC4 serves the C4 projection of one model.

Read-only by construction: there is no write counterpart and there never will be one under this record. ArchiMate stays the only authored notation (ADR-0189 §7).

func (*Service) HandleContext

func (s *Service) HandleContext(w http.ResponseWriter, r *http.Request)

HandleContext answers "has it been like this" for one element (ADR-0189 P5b).

It writes nothing and stores nothing — the stores it reads are external, owned by somebody else, and retained on their own terms. That is the whole point: ADR-0189 rejected copying remote metrics and logs into a Panorama database by name, so this asks the question every time rather than keeping the answer.

func (*Service) HandleCreate

func (s *Service) HandleCreate(w http.ResponseWriter, r *http.Request)

HandleCreate imports an Open Exchange document into an application-owned Panorama model. The XML is validated but stored byte-for-byte.

func (*Service) HandleDelete

func (s *Service) HandleDelete(w http.ResponseWriter, r *http.Request)

HandleDelete removes a model. A missing or inaccessible model is a 404 so an application scope the caller cannot see is not disclosed.

func (*Service) HandleDrift

func (s *Service) HandleDrift(w http.ResponseWriter, r *http.Request)

HandleDrift returns what has been seen to change about one model, newest first.

It is a separate route from the observations because the two answer different questions and cost different things: "what is happening" needs the engine read, "what changed" is a read of what previous answers already established. A caller watching a landscape asks the first often and the second rarely.

func (*Service) HandleGet

func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)

HandleGet returns one model's metadata without its XML.

func (*Service) HandleList

func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)

HandleList lists metadata for every model the caller may view. XML is loaded from disk as part of the sidecar record but is never copied into the response.

func (*Service) HandleObservations

func (s *Service) HandleObservations(w http.ResponseWriter, r *http.Request)

HandleObservations projects a model's Atlas bindings onto what this server currently sees (ADR-0189 §6).

It is a read of the model plus a read of the engine, and it writes nothing: the stored document is not touched, and no observation is persisted. That is the rule the record states as "the declarative XML is never mutated by polling", and the reason this is a separate route rather than a field on the model — a caller who wants the drawing must be able to have it without paying for a scan of every live instance.

func (*Service) HandleSetBinding

func (s *Service) HandleSetBinding(w http.ResponseWriter, r *http.Request)

HandleSetBinding sets one binding key on one element, replacing whatever that key held. An empty values list clears it. Everything else in the document — Atlas's other keys, foreign properties, formatting, unsupported standard content — is left byte-for-byte alone.

func (*Service) HandleSetLayout

func (s *Service) HandleSetLayout(w http.ResponseWriter, r *http.Request)

HandleSetLayout writes the new positions of shapes on a view (ADR-0189 §2, P2a).

It takes the same rights as any other write to the model, and the same revision check: two people arranging one view is exactly the case where a lost update would be invisible — the loser's boxes simply drift back, with nothing to say why.

func (*Service) HandleSubset

func (s *Service) HandleSubset(w http.ResponseWriter, _ *http.Request)

HandleSubset serves the authoring subset: what may be created, what may be drawn between what, and the statement that it is a subset (ADR-0189 §2, P2b).

It is served rather than duplicated in the browser, and that is the whole reason this route exists. The canvas has to refuse a connection while it is being dragged; the server has to refuse it on write. Two copies of a relationship matrix is how you get a canvas that lets somebody draw an arrow the server then rejects — so there is one table, and the browser is given it.

It reads no model and takes no id: the subset is a property of this build, not of anybody's document, and asking for it discloses nothing about what exists.

func (*Service) HandleUpdate

func (s *Service) HandleUpdate(w http.ResponseWriter, r *http.Request)

HandleUpdate saves a new revision when expectedRevision still matches. The check and atomic save happen in one run-loop turn, so two browser sessions cannot both overwrite the same revision successfully.

func (*Service) HandleValidate

func (s *Service) HandleValidate(w http.ResponseWriter, r *http.Request)

HandleValidate checks an Open Exchange XML document without storing it.

func (*Service) HandleXML

func (s *Service) HandleXML(w http.ResponseWriter, r *http.Request)

HandleXML exports the canonical Open Exchange document unchanged.

func (*Service) OverlaysOnLoop

func (s *Service) OverlaysOnLoop(r *http.Request) ([]Overlay, error)

OverlaysOnLoop returns every model this caller may view, as overlays for the landscape mesh (ADR-0211 §11). It is a composition-root hook like the counting helpers: callers must already be executing on the Service's run loop, as the name says, so it deliberately does not call Loop.Do recursively.

A model whose XML no longer parses is skipped rather than failing the whole overlay: one broken document must not blank out the comparison for every other model. Its bindings are simply not there to compare, which the mesh already has a vocabulary for.

func (*Service) WithContextResolver

func (s *Service) WithContextResolver(resolve ContextResolver) *Service

WithContextResolver wires the historical-context adapters onto a service.

It is a separate call rather than another parameter of New because it is genuinely optional: a server with no log store and no metrics store is a complete Atlas, and its Panorama answers every other question exactly as before.

type Status

type Status struct {
	OK        int `json:"ok"`
	Attention int `json:"attention"`
	Critical  int `json:"critical"`
	// Unknown counts the nodes with no applicable observation. It is stated rather
	// than folded into ok, because "nothing is wrong here" and "nothing here is
	// watched" are the two answers a status view must never merge.
	Unknown int `json:"unknown"`
	// Partial reports that the incident scan hit its bound before it finished, so
	// some parked work went uncounted and a node's ok is a floor rather than a
	// verdict.
	Partial bool `json:"partial,omitempty"`
	// Unavailable names the observation states this build cannot produce at all.
	Unavailable []UnavailableState `json:"unavailable"`
}

Status summarizes the severity of one derived mesh.

type Store

type Store struct {
	*sidecar.Store[Model]
}

Store persists Panorama models as atomic, fsynced design-time sidecars. It is owned by the API run loop and performs no locking of its own (ADR-0189/I3).

func NewStore

func NewStore(dir string) (*Store, error)

NewStore opens the Panorama model directory. Models list newest first, with a stable id tie-breaker. IDs name their files directly and therefore must have the opaque hexadecimal shape produced by token.New.

func (*Store) ForApplication

func (s *Store) ForApplication(applicationID string) ([]Model, error)

ForApplication returns one application's models newest first.

type Subset

type Subset struct {
	Version       int                `json:"version"`
	Elements      []ElementKind      `json:"elements"`
	Relationships []RelationshipKind `json:"relationships"`
	// Matrix is the permitted relationship types for every ordered pair of element
	// types in the subset, keyed "Source>Target". It is precomputed and sent whole
	// rather than asked for pair by pair: the canvas needs an answer during a drag,
	// and a network round trip per pointer move is not an answer.
	Matrix map[string][]string `json:"matrix"`
	// Limits says what this subset is not, in the same shape every other Panorama
	// answer publishes its limits.
	Limits []SubsetLimit `json:"limits"`
}

Subset is the whole contract, as one document for the browser: what may be created, what may be drawn, and the statement that it is a subset.

func AuthoringSubset

func AuthoringSubset() Subset

AuthoringSubset builds the contract document.

type SubsetLimit

type SubsetLimit struct {
	Limit  string `json:"limit"`
	Reason string `json:"reason"`
}

SubsetLimit is one thing this subset does not do, with the reason.

type Summary

type Summary struct {
	ID            string `json:"id"`
	ApplicationID string `json:"applicationId"`
	Name          string `json:"name"`
	Notation      string `json:"notation"`
	Revision      int64  `json:"revision"`
	CreatedAt     int64  `json:"createdAt"`
	CreatedBy     string `json:"createdBy,omitempty"`
	UpdatedAt     int64  `json:"updatedAt"`
	UpdatedBy     string `json:"updatedBy,omitempty"`
}

Summary is the list/read representation of a Model. The large XML document is intentionally absent and is fetched only through the dedicated export route.

type Target

type Target struct {
	ID   string
	Name string
	// State is the observation state ReachOut resolved (ADR-0189 §6), and Reason the
	// sentence behind it. Empty means the peer was never asked — which is not the
	// same as unreachable, and is what a landscape derived while the loop was closing
	// would carry.
	State  string
	Reason string
}

Target is one deployment target and what this server currently knows of it.

It carries no base URL and no credential reference: those are this operator's map of where their infrastructure lives, and a landscape is opened by anybody with modeler access. The name is disclosed to every caller, which is the same rule the binding catalog already applies — a deployment target is org-wide infrastructure with no sharing scope of its own.

No edges are derived to it, and that absence is deliberate rather than pending. A promotion is an act, not a stored relationship: this server does not record which of its applications is running over there, so any line drawn from one to a target would be an assertion nobody made. What it does know is that the peer exists and whether it answers, and that is exactly what is drawn.

type UnavailableState

type UnavailableState struct {
	State  string `json:"state"`
	Reason string `json:"reason"`
}

UnavailableState is one observation state this build cannot produce, with the reason it cannot. It is in the payload rather than in the documentation because the consumer of a status picture is the one who needs to know what the absence of a finding is worth.

type ValidationResult

type ValidationResult struct {
	Valid           bool      `json:"valid"`
	Notation        string    `json:"notation"`
	Namespace       string    `json:"namespace,omitempty"`
	ModelIdentifier string    `json:"modelIdentifier,omitempty"`
	Name            string    `json:"name,omitempty"`
	Elements        int       `json:"elements"`
	Relationships   int       `json:"relationships"`
	Views           int       `json:"views"`
	Problems        []Problem `json:"problems"`
}

ValidationResult is the bounded summary returned by Panorama validation. Counts describe reusable semantic elements and relationships plus diagram views; they do not duplicate the model into an Atlas-specific metamodel.

func Validate

func Validate(data []byte) ValidationResult

Validate checks the Open Exchange envelope, the standard ArchiMate type vocabulary, global identifier uniqueness, and the references used by relationships and diagram objects. It never rewrites the input: a model that passes is stored and exported byte-for-byte so unsupported-but-standard properties and visual metadata cannot disappear in a round trip.

Full XSD conformance fixtures and the ArchiMate relationship matrix remain a separate interoperability layer. This validator is the safe runtime gate: it is pure Go, bounded, rejects DTD/directive input, and performs no network or external-entity resolution.

type Worker

type Worker struct {
	ID             string
	Name           string
	Type           string
	CanView        bool
	Endpoint       string
	CredentialsRef string
	// State and Reason are this worker's observation, as on [Process].
	State  string
	Reason string
}

Worker is one configured worker as the mesh sees it — a target and identity of a Worker Type (ADR-0203), which is what Type names. Endpoint and CredentialsRef are carried so the derivation can be tested for *not* emitting them: a landscape picture is opened by anyone with modeler access, and an internal hostname is precisely what ADR-0211 §10 keeps out of what leaves the server. Neither field ever reaches a Node.

type WorkerUse

type WorkerUse struct {
	ElementID string
	Name      string
	TargetID  string
}

WorkerUse is one model reference to a configured worker, already resolved by the server. A model names a worker by name and never carries an endpoint or a secret (ADR-0036/0041), so nothing inside it can tell whether that name is configured anywhere — which is exactly why the references are enumerable from outside (ADR-0158), and why the mesh can answer it. TargetID is empty when no worker by that name is configured.

Jump to

Keyboard shortcuts

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