Documentation
¶
Overview ¶
Package infomodel holds Atlas's process information model: a UML class-diagram subset that gives BPMN's data objects a type they can share across processes (ADR-0230).
BPMN scopes a data object to one process definition and leaves its type slot — `itemSubjectRef` → `<itemDefinition structureRef="…">` — deliberately opaque, pointing "into some other schema language". So two processes that both handle an order have two unrelated strings named `order`, and nothing anywhere says they mean the same thing. This package is the other schema language: an application-owned document of classes, their attributes, and the associations between them, against which `itemSubjectRef` resolves.
It models *concepts, not storage.* An entity-relationship diagram's whole vocabulary — entities, columns, foreign keys — is persistence, and drawing one would assert a decision nobody has made. Where a datum is persisted is the data store's question (ADR-0036), settled per store; this is about what a datum *is*.
Index ¶
- Constants
- func AllowedFrom(fromStereotype, toStereotype string) []string
- func BusinessKey(vocab *Vocabulary, className string, value json.RawMessage) string
- func CheckDataFlow(cp *compiler.CompiledProcess, vocab *Vocabulary) []compiler.Problem
- func DetectImportFormat(document []byte) (string, bool)
- type AccessResolver
- type ApplicationAccess
- type Association
- type AssociationKind
- type Attribute
- type Class
- type Clock
- type DataStore
- type End
- type Finding
- type Graph
- type IDGenerator
- type ImportNote
- type ImportResponse
- type ImportResult
- type Lifecycle
- type LifecycleRules
- type LifecycleState
- type LifecycleTransition
- type LossNote
- type Model
- type MultiplicityOption
- type ObjectAttribute
- type ObjectLink
- type ObjectNode
- type ObjectValue
- type PrimitiveType
- type Projection
- type Refusal
- type Service
- func (s *Service) HandleCreate(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleDelete(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleImport(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleSchema(w http.ResponseWriter, r *http.Request)
- func (s *Service) HandleSubset(w http.ResponseWriter, _ *http.Request)
- func (s *Service) HandleUpdate(w http.ResponseWriter, r *http.Request)
- func (s *Service) VocabularyOnLoop(applicationID string) (*Vocabulary, error)
- type StereotypeKind
- type Store
- type StoreMode
- type Subset
- type SubsetLimit
- type Summary
- type UnresolvedRef
- type ValidationResult
- type Vocabulary
Constants ¶
const ( // RuleDataUnresolvedType marks a data object whose itemSubjectRef names no class // in the owning application's information model. RuleDataUnresolvedType = "data.unresolved-type" // RuleDataUntyped marks a data object that declares no type at all, in an // application that does model its data. RuleDataUntyped = "data.untyped" // RuleDataUnknownMember marks a write targeting a member the resolved class has // no attribute for. RuleDataUnknownMember = "data.unknown-member" // RuleDataMemberThroughScalar marks a dotted write path that walks through a // primitive or an enumeration, which has no members to walk into. RuleDataMemberThroughScalar = "data.member-through-scalar" // RuleDataMemberThroughCollection marks a dotted write path that walks through a // collection attribute — legal to write, but it sets a member of the list value // rather than of each element (ADR-0060 leaves list indexing to a follow-up). RuleDataMemberThroughCollection = "data.member-through-collection" // RuleDataNeverWritten marks a data object an activity reads that nothing in the // process ever writes. RuleDataNeverWritten = "data.never-written" // RuleDataReadBeforeWrite marks a read no writer can precede — the parallel // branch that reads what the other branch produces. RuleDataReadBeforeWrite = "data.read-before-write" // RuleDataUnknownStore marks a <dataStoreReference> naming a store the owning // application's information model does not declare. RuleDataUnknownStore = "data.unknown-store" // RuleDataStoreUnbound marks a store that is modeled but has no Worker behind it, // so nothing can actually reach what it holds. RuleDataStoreUnbound = "data.store-unbound" // RuleDataUnknownState: a process moves an object into a state its class's // lifecycle does not declare (ADR-0259). The typo check, and the one that finds // something on the first application it runs against. RuleDataUnknownState = "data.unknown-state" // RuleDataIllegalTransition: both states are declared and no transition joins // them. Deliberately conservative — see checkLifecycles for what it stays quiet // about, and why that is the right direction to be wrong in. RuleDataIllegalTransition = "data.illegal-transition" )
The rule slugs. Like the compiler's own, they are stable machine names a UI can group and filter by without parsing the message.
const ( ImportFormatJSON = "json" ImportFormatXMI = "xmi" )
The formats an import may arrive in.
const ( // NoteDropped: the element is not in the imported model at all. NoteDropped = "dropped" // NoteAdjusted: the element is here, saying something slightly different from // what the document said — a widened multiplicity, a type read as text. NoteAdjusted = "adjusted" // NoteInfo: nothing was lost; this is a fact about the import worth stating. NoteInfo = "info" )
What became of one element of the source document. The three levels are what a reader acts on differently: something is gone, something is here but not as it was written, or something is worth knowing.
const ( ViaContainment = "containment" ViaKey = "key" )
How a link was established. It travels to the reader because the two are different claims: one is read off the value, the other is inferred from a key.
const ( // StereotypeBusinessObject is a thing the business tracks and that has an // identity of its own: an Order, a Customer, a Claim. The default. StereotypeBusinessObject = "businessObject" // StereotypeValueType is a structured value with no independent existence: an // Address, a Money amount. Two of them with equal contents *are* the same value, // which is why one cannot declare a business key and cannot own parts. StereotypeValueType = "valueType" // StereotypeEnumeration is a closed set of literals: an OrderStatus. It has // literals where the others have attributes, and it appears only as an // attribute's type — never at the end of an association. StereotypeEnumeration = "enumeration" )
The stereotypes a class may carry. They are not decoration: almost every rule in the matrix below is about which of these three a class is.
const ( KindAssociation = "association" KindAggregation = "aggregation" KindComposition = "composition" KindGeneralization = "generalization" )
The association kinds. Four, because these are the four that say something a reader can act on; UML's dependency, realization and usage say things about code rather than about a business record.
const ( MultOptional = "0..1" MultOne = "1" MultMany = "0..*" MultAtLeast1 = "1..*" )
The multiplicities an attribute or an association end may carry.
const ( TypeString = "string" TypeNumber = "number" TypeBoolean = "boolean" TypeDate = "date" TypeDateTime = "dateTime" TypeTime = "time" TypeDuration = "duration" )
The primitive attribute types. Deliberately few and deliberately abstract: a process information model says a delivery date is a date, not that it is a DATETIME2(7) — the moment it says the latter it has become a storage schema.
const ( RefusedOutOfSubset = "out-of-subset" RefusedByNotation = "notation" )
Why something was refused. The distinction is the point: RefusedOutOfSubset is a limit of this build and reads as "not yet"; RefusedByNotation is a fact about what these things are and reads as "no".
const ( CodeMissingClassName = "missing-class-name" CodeDuplicateClassName = "duplicate-class-name" CodeDuplicateClassID = "duplicate-class-id" CodeUnknownStereotype = "unknown-stereotype" CodeMissingAttributeName = "missing-attribute-name" CodeDuplicateAttribute = "duplicate-attribute" CodeUnknownType = "unknown-type" CodeUnknownMultiplicity = "unknown-multiplicity" CodeAttributeTypedAsSelf = "attribute-typed-as-self" CodeEnumerationHasAttributes = "enumeration-has-attributes" CodeEnumerationEmpty = "enumeration-empty" CodeDuplicateLiteral = "duplicate-literal" CodeIdentityNotAllowed = "identity-not-allowed" CodeUnknownIdentityAttribute = "unknown-identity-attribute" CodeIdentityNotRequired = "identity-not-required" CodeIdentityNotSingular = "identity-not-singular" CodeUnknownClassRef = "unknown-class-ref" CodeRelationshipRefused = "relationship-refused" CodeSelfGeneralization = "self-generalization" CodeGeneralizationCycle = "generalization-cycle" CodeDuplicateAssociationID = "duplicate-association-id" CodeStoreMissingName = "store-missing-name" CodeDuplicateStoreName = "duplicate-store-name" CodeDuplicateStoreID = "duplicate-store-id" CodeStoreUnknownClass = "store-unknown-class" CodeStoreClassNotStorable = "store-class-not-storable" CodeStoreClassHasNoKey = "store-class-has-no-key" CodeStoreUnknownMode = "store-unknown-mode" // Lifecycles (ADR-0259). A lifecycle on disk has to be one a deploy can resolve // against: every state a transition names exists, one state is where an instance // starts, and no two states answer to the same name — a process writes a state by // that string and by nothing else. CodeLifecycleNotAllowed = "lifecycle-not-allowed" CodeLifecycleEmpty = "lifecycle-empty" CodeMissingStateName = "missing-state-name" CodeDuplicateStateName = "duplicate-state-name" CodeNoInitialState = "no-initial-state" CodeManyInitialStates = "many-initial-states" CodeUnknownTransitionState = "unknown-transition-state" CodeDuplicateTransitionID = "duplicate-transition-id" CodeTransitionLeavesFinalState = "transition-leaves-final-state" )
The finding codes. They are a public contract — the canvas keys its messages and its marks off them — so a code is added rather than repurposed.
const (
StoreModeRead = "read"
)
What a process may do with a store. Read-only today: writing through a store is a transaction against something outside the engine, which is a decision of its own and gets its own record rather than being smuggled in as a second enum value.
const SubsetVersion = 1
SubsetVersion is the version of the tables 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 ¶
This section is empty.
Functions ¶
func AllowedFrom ¶
AllowedFrom lists the association kinds that may run from one stereotype to another — what the canvas offers in its connect menu, so it never offers a line the server would refuse.
func BusinessKey ¶
func BusinessKey(vocab *Vocabulary, className string, value json.RawMessage) string
BusinessKey renders one data object's business key: the value of the attributes its class declares as its identity, joined. "" when the class is unknown, declares no key, or the value does not carry all of it.
This is the fact the cross-instance index is built on, and the reason a partial key is refused rather than joined from what is there: half a key identifies half as many things as a whole one only by accident, and an index that matched on it would quietly relate two orders that are not the same order.
func CheckDataFlow ¶
func CheckDataFlow(cp *compiler.CompiledProcess, vocab *Vocabulary) []compiler.Problem
CheckDataFlow inspects a compiled process against an application's vocabulary and returns every finding, in a deterministic order.
It runs at deploy and on the Problems panel's dry run — never at runtime. That is invariant I5 as it applies here: whether a write can land is knowable from the model and the vocabulary, so it is decided once, at deploy, and the engine keeps reading integer indices.
func DetectImportFormat ¶
DetectImportFormat reads the first meaningful byte, which is all it takes: a JSON document starts with an object and an XMI one with an element or a declaration. Sniffing rather than trusting a file extension is deliberate — a UML tool writes .uml, .xmi and .xml for the same document.
Types ¶
type AccessResolver ¶
type AccessResolver func(r *http.Request, applicationID string) (ApplicationAccess, error)
AccessResolver resolves application ownership on the API run loop. It is invoked only from a loop turn, so it may read the server's application store directly; it must not call Loop.Do recursively.
type ApplicationAccess ¶
ApplicationAccess is the caller's resolved access to the process application that owns a model. Like Panorama, this area reuses the application scope (ADR-0071/ 0128) rather than inventing a second ACL.
type Association ¶
type Association struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Kind string `json:"kind"`
// From is the source end. For a generalization it is the *specific* class ("an
// ExpressOrder is an Order"); for a composition or aggregation it is the whole.
From End `json:"from"`
To End `json:"to"`
}
Association relates two classes. Its Kind carries the meaning; the ends carry the reading — a role name and a multiplicity each, so the diagram states "a Customer places 0..* Orders" rather than only that a line exists.
type AssociationKind ¶
type AssociationKind struct {
Kind string `json:"kind"`
Label string `json:"label"`
// Rule is the sentence this implements, in plain words, so a refusal can say
// *why* rather than only that it was refused.
Rule string `json:"rule"`
// Directed reports whether the two ends mean different things, so the canvas
// knows whether reversing the arrow changes the statement.
Directed bool `json:"directed"`
}
AssociationKind is one drawable relationship.
func AssociationKinds ¶
func AssociationKinds() []AssociationKind
AssociationKinds exposes the relationship table. Everything else about the subset reaches a caller through AuthoringSubset, which is the payload the browser is served; this one is separate because the matrix is built from it. It returns the package's own slice; callers must not mutate it.
type Attribute ¶
type Attribute struct {
Name string `json:"name"`
// Type is a primitive (see PrimitiveTypes) or the name of another class in the
// same model — including an «enumeration», which is how a closed set of values
// is declared.
Type string `json:"type"`
Multiplicity string `json:"multiplicity"`
Documentation string `json:"documentation,omitempty"`
}
Attribute is one typed member of a class.
type Class ¶
type Class struct {
// ID is stable across renames, so an association survives its class being
// renamed; Name is what a model and a diagram refer to, and what
// `itemSubjectRef` matches.
ID string `json:"id"`
Name string `json:"name"`
Documentation string `json:"documentation,omitempty"`
// Stereotype says what kind of type this is, and it is what most of the
// relationship rules in subset.go are actually about — see the constants there.
Stereotype string `json:"stereotype"`
Attributes []Attribute `json:"attributes"`
// Literals are the members of an «enumeration», which has literals where every
// other stereotype has attributes. Empty otherwise.
Literals []string `json:"literals,omitempty"`
// Identity names the attributes that together form the business key: the fact
// that makes Order#ORD-1 the same order in three processes and in a data store.
// It is the part BPMN has no equivalent for, and every cross-process capability
// rests on it. Empty for a class whose instances have no identity of their own.
Identity []string `json:"identity,omitempty"`
// Lifecycle is the states this class's instances move through, and what a BPMN
// data state resolves against (ADR-0259). It sits here beside the business key
// because both are facts about a business object that BPMN has no field for, and
// because a lifecycle scoped to one process would reproduce the very problem the
// information model exists to solve.
//
// nil is the normal case and means silence everywhere: a class without one is
// exactly as it was before this existed.
Lifecycle *Lifecycle `json:"lifecycle,omitempty"`
// X and Y place the class on the canvas. Layout is part of the document because
// a diagram a person arranged is a diagram they can read again.
X float64 `json:"x"`
Y float64 `json:"y"`
}
Class is one business object type — the thing a BPMN data object's `itemSubjectRef` names.
type DataStore ¶
type DataStore struct {
ID string `json:"id"`
Name string `json:"name"`
Documentation string `json:"documentation,omitempty"`
// Class is the class whose instances this store holds. It must be a business
// object with a business key: a store you cannot address something in by its
// identity is not a store a process can read from.
Class string `json:"class"`
// Worker is the configured Worker that backs it (ADR-0036/0203) — a clio event
// store, a database, a SharePoint list. Empty while the store is modeled but not
// yet wired to anything, which is a normal state and not an error: a store is
// routinely drawn before somebody configures the worker behind it.
Worker string `json:"worker,omitempty"`
// Mode is what a process may do with it. Read-only today; writing through a store
// is a decision of its own (transactionality against something outside the engine)
// and gets its own record.
Mode string `json:"mode"`
X float64 `json:"x"`
Y float64 `json:"y"`
}
DataStore is a place instances of one class outlive the process that made them.
It is the one part of this model that is *not* storage-agnostic, and that is the point of separating it from the classes: an Order is an Order wherever it is kept, and the store is what says where. Everything about persistence lives here and nowhere else in the document.
type End ¶
type End struct {
ClassID string `json:"classId"`
Role string `json:"role,omitempty"`
Multiplicity string `json:"multiplicity,omitempty"`
}
End is one side of an association: which class, what it is called from the other side, and how many of it there are.
type Finding ¶
type Finding struct {
Code string `json:"code"`
Reason string `json:"reason"`
Message string `json:"message"`
// Exactly one of ClassID / AssociationID / StoreID identifies where the finding
// is; Attribute names the member within a class when the finding is about one.
ClassID string `json:"classId,omitempty"`
AssociationID string `json:"associationId,omitempty"`
StoreID string `json:"storeId,omitempty"`
Attribute string `json:"attribute,omitempty"`
// State and Transition locate a finding inside a class's lifecycle, the way
// Attribute locates one inside its members — so the state canvas can mark the
// state or the arrow that is wrong instead of colouring the whole class.
State string `json:"state,omitempty"`
Transition string `json:"transition,omitempty"`
}
Finding is one thing wrong with a model, located precisely enough that the canvas can mark it rather than print a list somebody has to search.
type Graph ¶
type Graph struct {
Nodes []ObjectNode `json:"nodes"`
Links []ObjectLink `json:"links"`
Unresolved []UnresolvedRef `json:"unresolved"`
// Degraded says the application models nothing, so this is the instance's data
// without the structure a model would give it.
Degraded bool `json:"degraded"`
// Truncated says a guard stopped the walk, so the picture is a part of one.
Truncated bool `json:"truncated"`
}
Graph is the whole derived picture, with what it could not show.
func ObjectGraph ¶
func ObjectGraph(objects []ObjectValue, vocab *Vocabulary) Graph
ObjectGraph derives one instance's object diagram from its data objects and the application's vocabulary.
type IDGenerator ¶
IDGenerator mints an opaque model or element id off the run loop.
type ImportNote ¶
type ImportNote struct {
Level string `json:"level"`
// Element names the thing in the source document — a class, an attribute, an id
// — so a reader can find it in the tool the document came from.
Element string `json:"element,omitempty"`
Message string `json:"message"`
}
ImportNote is one thing the import could not carry, or carried differently.
type ImportResponse ¶
type ImportResponse struct {
Format string `json:"format"`
Notes []ImportNote `json:"notes"`
Validation ValidationResult `json:"validation"`
// Preview is the model the document would become, present on a dry run only.
Preview *Model `json:"preview,omitempty"`
// Model is the stored model, present when one was created.
Model *Summary `json:"model,omitempty"`
}
ImportResponse is what an import answers with — and the notes are the substance of it, not a footnote: an import from a foreign notation is a lossy operation, and the list of what it lost is the part a modeler has to read.
type ImportResult ¶
type ImportResult struct {
Format string `json:"format"`
Model Model `json:"model"`
Notes []ImportNote `json:"notes"`
}
ImportResult is a parsed document: the model it became, and the honest account of the difference between the two.
func ParseImport ¶
func ParseImport(format string, document []byte) (ImportResult, error)
ParseImport turns a document into a storable model and the account of what the import did to it. An empty format is detected from the document itself.
It never returns a model Validate would refuse: everything outside the subset is dropped or adjusted here, with a note, rather than being handed to the store and refused there — a refusal names a rule, and a person importing somebody else's diagram needs to know which of *their* elements it was about.
type Lifecycle ¶ added in v0.6.0
type Lifecycle struct {
States []LifecycleState `json:"states"`
Transitions []LifecycleTransition `json:"transitions"`
}
Lifecycle is the state machine a business object moves through, and the thing a BPMN data state resolves against (ADR-0259).
BPMN's `<dataObjectReference>` carries a data state — the `[received]`, `[approved]` in square brackets under the box — and says nothing whatever about what states exist or which may follow which. It is the second opaque slot beside `itemSubjectRef`, and this is what fills it: a class declares the states its instances can be in, and a deploy resolves every state a process writes against that declaration.
It is *not* an «enumeration» attribute. An enumeration is a closed set of values something is typed as; a lifecycle is a set of stages with an order between them, and the order is most of what it says. "An order may be cancelled, but not once it has shipped" is a lifecycle, and is not expressible as a type.
Nothing executes it. Atlas runs BPMN; a lifecycle constrains data that BPMN moves, and is resolved at deploy exactly as a class name is (ADR-0259 §5).
type LifecycleRules ¶ added in v0.6.0
type LifecycleRules struct {
// Stereotypes that may carry one. Only a business object has an identity that
// persists through states; a value type is equal to any other with the same
// contents, and an enumeration is a set of values rather than a thing that moves
// through them.
Stereotypes []string `json:"stereotypes"`
// Exactly one state is where an instance is created, and any number are where
// instances end. Served rather than assumed, because "one start" is a rule the
// canvas has to enforce while drawing and not only report afterwards.
InitialStates int `json:"initialStates"`
// SelfTransitions says a state may transition to itself: a record can be revised
// without leaving the stage it is in.
SelfTransitions bool `json:"selfTransitions"`
// LeavesFinal says whether a transition may start at a final state. It may not —
// "final" and "leaves" are the two saying opposite things.
LeavesFinal bool `json:"leavesFinal"`
// Guards says whether a transition may carry a condition. It may not, and the
// reason travels with the answer: what causes a transition is the BPMN element
// that writes the state, and a condition here would be a second place deciding
// what happens.
Guards bool `json:"guards"`
Meaning string `json:"meaning"`
}
LifecycleRules is what the state canvas may draw, served rather than restated (ADR-0259 §2). The canvas refuses mid-drag exactly what the write path refuses, in the server's own words — which is the arrangement the class canvas already has for its relationship matrix, and the reason a refusal can say "UML allows this, this build does not" rather than pretending the standard forbids it.
func Lifecycles ¶ added in v0.6.0
func Lifecycles() LifecycleRules
Lifecycles reports the rules a state machine is held to.
type LifecycleState ¶ added in v0.6.0
type LifecycleState struct {
Name string `json:"name"`
Documentation string `json:"documentation,omitempty"`
// Initial marks where an instance starts. Exactly one state carries it: a machine
// with no start says nothing about what an object is when it is created, and one
// with two says two contradictory things.
Initial bool `json:"initial,omitempty"`
// Final marks a state nothing leaves. Any number of them, including none — an
// order ends either shipped or cancelled, and a long-lived record may end nowhere.
Final bool `json:"final,omitempty"`
// X and Y place the state on the canvas, for the reason a class carries them: a
// diagram somebody arranged is a diagram they can read again.
X float64 `json:"x"`
Y float64 `json:"y"`
}
LifecycleState is one stage in a class's life.
Its Name is its identity, deliberately, where an association names its ends by a stable id. The name here *is* what a process writes: a `<dataState name="approved">` matches it by that string, so an id beside it would be a second identity for one thing, and renaming a state is renaming what every process has to write. The same reasoning makes Class.Identity name attributes and DataStore.Class name a class.
type LifecycleTransition ¶ added in v0.6.0
type LifecycleTransition struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
// From and To are state names within this lifecycle. A transition to the state it
// starts from is a self-loop and is allowed: a record can be revised without
// leaving the stage it is in.
From string `json:"from"`
To string `json:"to"`
}
LifecycleTransition is one move a class's instances may make between its states.
Its Name is documentation and nothing more. What *causes* a transition is the BPMN element that writes the state, and that fact lives in the process — a condition here would be a second place where something decides what happens, and the first is the sequence flow (ADR-0259 §5).
type LossNote ¶
LossNote is one thing the projection could not express, and why. The reason matters more than the fact: "JSON Schema has no keyword for this" is a property of the target notation, not a shortcoming of the model it came from.
type Model ¶
type Model struct {
ID string `json:"id"`
ApplicationID string `json:"applicationId"`
Name string `json:"name"`
Documentation string `json:"documentation,omitempty"`
// Revision guards a concurrent overwrite: a write states the revision it read,
// and a write against a stale one is refused rather than silently winning.
Revision int64 `json:"revision"`
Classes []Class `json:"classes"`
Associations []Association `json:"associations"`
// Stores are where instances of a class live beyond one process instance — the
// cross-process channel BPMN gestures at with <dataStoreReference> and then says
// nothing about. They live in this document because a store is per application,
// declared once and named by every process that reaches it; and beside the
// classes rather than inside one, because a class stays storage-agnostic. Only
// the store says where its instances are kept.
Stores []DataStore `json:"stores"`
CreatedAt int64 `json:"createdAt"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedAt int64 `json:"updatedAt"`
UpdatedBy string `json:"updatedBy,omitempty"`
}
Model is one application-owned information model. Unlike a Panorama document — whose canonical form is the standard's own interchange XML — this one is stored in its native shape and *projected* to its notations: a UML class diagram to read, and a JSON Schema to validate against. A projection is derived, never authored, and states what it dropped.
type MultiplicityOption ¶
type MultiplicityOption struct {
Multiplicity string `json:"multiplicity"`
Label string `json:"label"`
// Required and Collection are what the JSON Schema projection and the canvas
// both need, derived here once rather than by parsing the string in two places.
Required bool `json:"required"`
Collection bool `json:"collection"`
}
MultiplicityOption is one multiplicity, with what it means for a value.
func MultiplicityOf ¶
func MultiplicityOf(m string) (MultiplicityOption, bool)
MultiplicityOf returns one multiplicity's meaning, and whether it is in the subset at all.
type ObjectAttribute ¶
type ObjectAttribute struct {
Name string `json:"name"`
Value string `json:"value"`
Key bool `json:"key,omitempty"`
// Absent distinguishes "this object does not carry that member" from "it carries
// it, empty" — different facts about a datum, and the first is usually the one
// worth noticing.
Absent bool `json:"absent,omitempty"`
}
ObjectAttribute is one member of an object node, in the order its class declares them — so two objects of one class read alike rather than in whatever order their JSON happened to be written.
type ObjectLink ¶
type ObjectLink struct {
From string `json:"from"`
To string `json:"to"`
Kind string `json:"kind"`
Label string `json:"label,omitempty"`
Via string `json:"via"`
}
ObjectLink is one line.
type ObjectNode ¶
type ObjectNode struct {
ID string `json:"id"`
Label string `json:"label"`
Name string `json:"name"`
Class string `json:"class,omitempty"`
State string `json:"state,omitempty"`
// Key is the object's business key, rendered — what makes this object *this*
// one, and what another object's reference has to match to become a line.
Key string `json:"key,omitempty"`
Attributes []ObjectAttribute `json:"attributes"`
// Value is what the object holds, rendered as an attribute's value is (a scalar
// as itself, a structure as its JSON). Carried for an object whose class is
// unknown or whose value is not a structure — there is no attribute list to show
// instead, and an object with neither would be an empty box.
Value string `json:"value,omitempty"`
// Nested marks a node that came out of a whole's value rather than being a data
// object of the instance in its own right.
Nested bool `json:"nested,omitempty"`
Unset bool `json:"unset,omitempty"`
}
ObjectNode is one object on the diagram.
type ObjectValue ¶
type ObjectValue struct {
Name string
// Class is the resolved declared type, "" when the object is untyped or its
// itemSubjectRef resolves to nothing.
Class string
State string
// Value is the object's value as canonical JSON, nil when it is unset.
Value json.RawMessage
}
ObjectValue is one data object of an instance, as the graph reads it.
type PrimitiveType ¶
type PrimitiveType struct {
Type string `json:"type"`
Label string `json:"label"`
// JSONType and JSONFormat are the projection: the draft 2020-12 type and the
// format keyword this primitive becomes. A primitive with no format projects to
// its bare type.
JSONType string `json:"jsonType"`
JSONFormat string `json:"jsonFormat,omitempty"`
}
PrimitiveType is one built-in attribute type, with the JSON Schema it projects to (jsonschema.go reads this rather than keeping a second list).
func PrimitiveOf ¶
func PrimitiveOf(t string) (PrimitiveType, bool)
PrimitiveOf returns one primitive's projection, and whether the name is one.
type Projection ¶
type Projection struct {
Class string `json:"class"`
Schema map[string]any `json:"schema"`
Loss []LossNote `json:"loss"`
}
Projection is a derived schema together with what deriving it cost.
func SchemaFor ¶
func SchemaFor(m Model, className string) (Projection, error)
SchemaFor projects one class of a model to a JSON Schema.
It validates the model first and refuses an invalid one. A schema derived from a model that does not yet make sense would be a confident, checkable, wrong statement about what a value must look like — worse than no schema.
type Refusal ¶
Refusal explains a rejected connection in words a modeler can act on.
func AllowAssociation ¶
AllowAssociation is the matrix: may an association of this kind run from a class of one stereotype to a class of another? It is the single authority — the canvas is served it, the server applies it on write.
It rules on *stereotypes*, not on individual classes, because every rule here is about what kind of thing each end is. Whether the two ends are the same class, and whether a generalization closes a cycle, are properties of a particular model rather than of the notation, so validation.go answers those.
type Service ¶
type Service struct {
// Limits are the installation's resource budgets. New sets them to
// [limits.Default]; the server overwrites them with its own once it has read the
// environment, so every ceiling in this service is the one operators configured
// (ADR-0291).
Limits limits.Limits
// contains filtered or unexported fields
}
Service serves the information-model area. Its only mutable dependency is the store, and every read and write of it goes through the loop — the design-time single-writer boundary (I3, ADR-0147).
func New ¶
func New(loop *runloop.Loop, store *Store, access AccessResolver, newID IDGenerator, now Clock) *Service
New builds the service.
func (*Service) HandleCreate ¶
func (s *Service) HandleCreate(w http.ResponseWriter, r *http.Request)
HandleCreate starts an empty information model for an application.
func (*Service) HandleDelete ¶
func (s *Service) HandleDelete(w http.ResponseWriter, r *http.Request)
HandleDelete removes a model.
func (*Service) HandleGet ¶
func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)
HandleGet returns one whole model, with its validation.
func (*Service) HandleImport ¶
func (s *Service) HandleImport(w http.ResponseWriter, r *http.Request)
HandleImport reads a UML class diagram into a new information model.
ADR-0230 left this out — "XMI is an export, not an interchange" — and ADR-0232 settles the other half: a model is routinely drawn in a UML tool before anybody opens Atlas, and retyping one by hand loses a business key quietly. What makes it safe is that the import is not trusted: it goes through the same subset the canvas writes through, everything outside that subset is dropped with a sentence naming the element, and a document that would produce a model the validator refuses is refused itself.
func (*Service) HandleList ¶
func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)
HandleList lists the models the caller may view, newest first.
func (*Service) HandleSchema ¶
func (s *Service) HandleSchema(w http.ResponseWriter, r *http.Request)
HandleSchema serves the JSON Schema projection of one class — the derived, read-only contract a value of that class is checked against, together with what the projection could not carry.
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.
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, and the server has to refuse it on write. It reads no model and takes no id: the subset is a property of this build, not of anybody's document, so asking for it discloses nothing about what exists.
func (*Service) HandleUpdate ¶
func (s *Service) HandleUpdate(w http.ResponseWriter, r *http.Request)
HandleUpdate replaces a model's content.
It takes the whole document rather than a patch, and refuses one that does not validate. A canvas edits a graph — moving a class, retyping an attribute, redrawing a line — and a patch language for that would be a second way to say everything the document already says. Refusing an invalid write is what keeps the store's guarantee simple: every model on disk is one the subset accepts, so a deploy resolving `itemSubjectRef` against it never meets a half-model.
func (*Service) VocabularyOnLoop ¶
func (s *Service) VocabularyOnLoop(applicationID string) (*Vocabulary, error)
VocabularyOnLoop is what an application's information models say, flattened for resolution — what a deploy and the Problems panel resolve `itemSubjectRef` against. It runs inside an existing loop turn, so callers must already hold one.
An application with no model yields an *unmodeled* vocabulary rather than an empty one, and the difference is deliberate: the checks that need a vocabulary say nothing at all against it, so an instance that has not started modeling does not get a warning on every data object it has.
type StereotypeKind ¶
type StereotypeKind struct {
Stereotype string `json:"stereotype"`
Label string `json:"label"`
// Meaning is the sentence a modeler needs to choose correctly. It travels to the
// browser because the difference between a business object and a value type is
// the single most consequential choice in this metamodel, and a palette that only
// lists three words makes it by accident.
Meaning string `json:"meaning"`
// HasIdentity reports whether this kind may declare a business key. Only a
// business object may: identity is what makes Order#ORD-1 the same order in three
// processes, and a value with no independent existence has none to declare.
HasIdentity bool `json:"hasIdentity"`
// HasAttributes is false for an enumeration, which carries literals instead.
HasAttributes bool `json:"hasAttributes"`
}
StereotypeKind is one authorable class kind, as the palette offers it.
type Store ¶
Store persists information models as atomic, fsynced design-time sidecars. It is owned by the API run loop and does no locking of its own (I3).
func NewStore ¶
NewStore opens the information-model directory. Models list newest first with a stable id tie-breaker, so a listing does not reshuffle between two reads that happened in the same second. IDs name their files directly and therefore must have the opaque hexadecimal shape token.New produces.
type StoreMode ¶
type StoreMode struct {
Mode string `json:"mode"`
Label string `json:"label"`
Meaning string `json:"meaning"`
}
StoreMode is one authorable store mode, as the palette offers it.
func StoreModeOf ¶
StoreModeOf returns one mode, and whether it is in the subset at all.
type Subset ¶
type Subset struct {
Version int `json:"version"`
Notation string `json:"notation"`
Stereotypes []StereotypeKind `json:"stereotypes"`
AssociationKinds []AssociationKind `json:"associationKinds"`
Primitives []PrimitiveType `json:"primitives"`
Multiplicities []MultiplicityOption `json:"multiplicities"`
StoreModes []StoreMode `json:"storeModes"`
// Lifecycles is the state machine a class may carry (ADR-0259).
Lifecycles LifecycleRules `json:"lifecycles"`
// Matrix is AllowAssociation precomputed for every stereotype pair, so the canvas
// can grey out a connection while it is being dragged without a round trip. Keyed
// "from>to".
Matrix map[string][]string `json:"matrix"`
Limits []SubsetLimit `json:"limits"`
}
Subset is the whole authoring contract, as the browser is served it.
func AuthoringSubset ¶
func AuthoringSubset() Subset
AuthoringSubset builds the payload served to the browser.
type SubsetLimit ¶
SubsetLimit is one thing this build does not author, and why. Stating them is what keeps "we do not do this yet" apart from "this is not a thing".
type Summary ¶
type Summary struct {
ID string `json:"id"`
ApplicationID string `json:"applicationId"`
Name string `json:"name"`
Documentation string `json:"documentation,omitempty"`
Revision int64 `json:"revision"`
Classes int `json:"classes"`
Associations int `json:"associations"`
Stores int `json:"stores"`
CreatedAt int64 `json:"createdAt"`
CreatedBy string `json:"createdBy,omitempty"`
UpdatedAt int64 `json:"updatedAt"`
UpdatedBy string `json:"updatedBy,omitempty"`
}
Summary is the listing representation: everything a library row shows, without the classes and associations a canvas needs.
type UnresolvedRef ¶
type UnresolvedRef struct {
From string `json:"from"`
Role string `json:"role"`
Class string `json:"class"`
Value string `json:"value"`
}
UnresolvedRef is a reference this instance cannot satisfy: a member holding a business key that names no object here.
type ValidationResult ¶
ValidationResult is the whole verdict. Findings are ordered by where they are, so a re-validation of an unchanged model produces an identical list.
func Validate ¶
func Validate(m Model) ValidationResult
Validate checks a whole model against the subset and against itself.
type Vocabulary ¶
type Vocabulary struct {
// contains filtered or unexported fields
}
Vocabulary is what an application's information models say, flattened for resolution: a class by name, and every attribute it has including the ones it inherits. Built once per check rather than walked per lookup, because a check resolves the same class once per association.
func NewVocabulary ¶
func NewVocabulary(models []Model) *Vocabulary
NewVocabulary flattens the models of one application. A later model wins a name clash, matching the order the store lists them in.
func (*Vocabulary) Class ¶
func (v *Vocabulary) Class(name string) (Class, bool)
Class resolves a declared type name.
func (*Vocabulary) Modeled ¶
func (v *Vocabulary) Modeled() bool
Modeled reports whether the application has any information model.