Documentation
¶
Overview ¶
Package dmn integrates the temis DMN decision engine (github.com/pblumer/temis) into Atlas, so a BPMN business rule task can delegate a decision and get an answer back.
The integration deliberately mirrors how service tasks reach external workers (ADR-0007), so it inherits the engine's durability guarantees without touching the hot path (ADR-0014):
- A DMN model is compiled by temis once, at deploy time, into immutable thread-safe decisions held in a Registry (invariant I5: compile, don't interpret — no XML parsing or FEEL compilation at runtime), and which deployment's model a task evaluates is settled there too (ADR-0319), so the runtime and a replay choose no version.
- A business rule task creates a job carrying the reserved DMN job type. The processor never evaluates a decision itself, so it stays allocation-free (invariant I1) and free of the temis dependency.
- The in-process Handler — a job worker — pulls those jobs, evaluates the decision off the processor goroutine, and completes the job, which drives the token onward through the normal completion path. Evaluation is a post-durability side effect, exactly like any other worker (invariant I2).
A business rule task's input context is the static inputs recorded at deploy time overlaid with its io-mapping inputs, FEEL-evaluated over the instance's variables off the hot path, and its result is written back into the process variable the task names (ADR-0039). Each evaluation also rides back as a durable record carrying its inputs, outputs and temis trace (ADR-0066); the caller-supplied sink is an additional observation seam for tests and diagnostics, not the path the result travels.
Index ¶
- Variables
- func BuildInputs(store state.Reader, elementKey, piKey uint64, staticJSON string, ...) (map[string]any, error)
- func DecisionHandler(store state.Reader, lookup ProcessLookup, bind Bind, sink func(Result)) job.CompletingHandler
- func EnsureDiagram(src []byte) []byte
- func EnsureDiagramReport(src []byte) ([]byte, bool)
- func Handler(store state.Reader, lookup ProcessLookup, reg *Registry, sink func(Result)) job.CompletingHandler
- func JSONObject(m map[string]any) string
- func OutputVariable(name string, outputs map[string]any) model.VariableValue
- func RegenerateDiagram(src []byte) []byte
- type Bind
- type DecisionField
- type DecisionInfo
- type DeployedDecision
- type DirResolver
- type Evaluation
- type Evaluator
- type GraphEdge
- type GraphNode
- type ModelGraph
- type ProcessLookup
- type Registry
- func (r *Registry) Deploy(defKey uint64, dmnXML []byte) error
- func (r *Registry) DeployDecision(key uint64, dmnXML []byte) error
- func (r *Registry) DeployedDecisions() []DeployedDecision
- func (r *Registry) Evaluate(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, error)
- func (r *Registry) EvaluateLatest(ctx context.Context, decisionId string, in map[string]any) (map[string]any, error)
- func (r *Registry) EvaluateLatestTraced(ctx context.Context, decisionId string, in map[string]any) (map[string]any, []byte, error)
- func (r *Registry) EvaluateTraced(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, []byte, error)
- func (r *Registry) Graph(defKey uint64, decisionId string) (ModelGraph, bool)
- func (r *Registry) IsService(defKey uint64, decisionId string) bool
- func (r *Registry) LatestDecisionIDs() map[string]bool
- func (r *Registry) LatestDecisionKey(decisionId string) (uint64, bool)
- func (r *Registry) Provides(defKey uint64, decisionId string) bool
- func (r *Registry) Reload(defKey uint64, dmnXML []byte) (string, error)
- func (r *Registry) ReloadDecision(key uint64, dmnXML []byte) (string, error)
- func (r *Registry) UndeployDecision(key uint64)
- type Resolver
- type Result
- type ServiceResolver
- type Standalone
- type Trial
- type ValidationResult
- type Validator
- func (v *Validator) Describe(ctx context.Context, modelRef string) (string, []DecisionInfo, error)
- func (v *Validator) Graph(ctx context.Context, modelRef string) (ModelGraph, error)
- func (v *Validator) Try(ctx context.Context, src []byte, decisionID string, inputs map[string]any) Trial
- func (v *Validator) Validate(ctx context.Context, modelRef string) (ValidationResult, error)
- func (v *Validator) ValidateXML(ctx context.Context, xml []byte) ValidationResult
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("dmn: no model matches the reference")
ErrNotFound is returned by a Resolver when no model matches the handle. It is deliberately distinct from an I/O failure so a caller can tell "unresolved reference" (a user-fixable modelRef) apart from "the model source is broken" (an infrastructure error).
Functions ¶
func BuildInputs ¶ added in v0.5.0
func BuildInputs(store state.Reader, elementKey, piKey uint64, staticJSON string, mappings []compiler.DecisionInputMapping) (map[string]any, error)
buildInputs assembles a decision's input context: the static constant inputs as a base, overlaid with the input mappings evaluated over the instance's live variables (a mapping overrides a static input of the same name). A nil result evaluates every referenced decision input to FEEL null.
The mappings resolve over the business rule task's full scope chain (elementKey up to the process root), not just the process scope, so a task nested in a subprocess or a multi-instance body reads its enclosing scope's variables — e.g. a per-row `inputElement` bound by a multi-instance loop (ADR-0068 scope-chain resolution, ADR-0077, ADR-0084). piKey binds the reserved processInstanceKey.
func DecisionHandler ¶
func DecisionHandler(store state.Reader, lookup ProcessLookup, bind Bind, sink func(Result)) job.CompletingHandler
DecisionHandler builds a job handler that evaluates the DMN decision behind a business rule task using the Evaluator that bind resolves for it. It owns the shared input/output-mapping semantics (ADR-0039) so local and central decisions cannot drift: for each job it
- resolves the decision, static inputs, input mappings, and result variable from the compiled process,
- builds the decision's input context by merging the static inputs with the input mappings evaluated over the instance's live variables (a mapping wins over a static input of the same name),
- evaluates the decision through the bound Evaluator, and
- returns the result as the process variable named by resultVariable, which the job completion writes back into the instance so a downstream gateway can route on it.
Evaluation is a post-durability side effect off the processor goroutine (invariant I2/I4). Returning an error leaves the job pending. Alongside the output variables, the completion carries a durable decision-evaluation record — the inputs, outputs, and trace — so an operator can inspect how the decision was made live and after the fact (ADR-0066). sink, if non-nil, additionally observes each result. Handler (local) and the temis worker (central, ADR-0050) are both built on it.
func EnsureDiagram ¶ added in v0.7.0
EnsureDiagram completes a DMN model's diagram so dmn-js can draw it, and returns the model unchanged when it already has one. Best-effort: anything it cannot parse or lay out comes back untouched rather than mangled.
func EnsureDiagramReport ¶ added in v0.7.0
EnsureDiagramReport is EnsureDiagram, additionally reporting whether it drew anything — which a caller storing the result wants to know.
Three cases, and the third is the one this exists for:
- the diagram covers every node → unchanged. Somebody placed these.
- there is no diagram → one is generated.
- the diagram covers *some* nodes → the whole graph is laid out afresh.
A partial diagram is not a half-finished human arrangement; no DMN tool emits one on purpose. It is the residue of a tool that drew what it could and saved back only that, so the coordinates in it were invented by the thing that could not draw the rest. Completing around them would mean placing the missing nodes in whatever space was left rather than laying out the graph the model describes.
func Handler ¶
func Handler(store state.Reader, lookup ProcessLookup, reg *Registry, sink func(Result)) job.CompletingHandler
Handler builds the in-process, local DMN worker: a DecisionHandler whose Evaluator is the embedded temis library, evaluating each decision against the model deployed under the process's own key (ADR-0014). Register it with a job.Runner via HandleCompleting for the reserved DMN job type (compiler.DMNJobTypeIndex). sink, if non-nil, observes each result.
func JSONObject ¶ added in v0.5.0
jsonObject renders a decision's input or output context as canonical JSON for a durable evaluation record. An empty context is recorded as "{}" (an object with no members) rather than JSON null, so the debugging view always shows an object; a marshal failure degrades the same way rather than failing the completion.
func OutputVariable ¶ added in v0.5.0
func OutputVariable(name string, outputs map[string]any) model.VariableValue
outputVariable turns a decision's outputs into the process variable named by the task's result variable. A single-output decision stores that value directly (so a condition reads it as a scalar); a multi-output decision stores the whole output map as a structured (JSON) context. The value is canonicalized through the same expr path as any other variable, so it round-trips on replay.
func RegenerateDiagram ¶ added in v0.7.0
RegenerateDiagram discards whatever diagram the model carries and lays the whole graph out afresh. It backs the editor's Auto-layout action: the author asking for it, which is the one thing that moves a diagram somebody placed.
Types ¶
type Bind ¶
type Bind func(cp *compiler.CompiledProcess, detail *compiler.BusinessRuleTaskDetail) (Evaluator, error)
Bind resolves the Evaluator for a business rule task on a compiled process — the decision engine bound to it. A returned error (e.g. an unregistered worker) leaves the job pending like any worker error.
type DecisionField ¶
DecisionField is one input or output of a decision, for authoring tooling: a name and its declared FEEL type (empty when the model declares none).
type DecisionInfo ¶
type DecisionInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Inputs []DecisionField `json:"inputs"`
Output DecisionField `json:"output"`
// Service marks a decision service rather than a decision: the same shape,
// because a business rule task calls either the same way, but an author choosing
// one should see which is the published interface and which is a decision inside
// it (services.go).
Service bool `json:"service,omitempty"`
// Members are the decisions a service publishes or evaluates internally, under
// the names a caller addresses them by; empty on a decision, which has none.
//
// It is here so a picker can show a decision as belonging to a service. Calling
// one of them works and answers correctly, which is exactly why it needs saying:
// it reaches past the interface the service exists to be, and ties the caller to
// an arrangement the service was meant to be free to change.
//
// Input decisions are deliberately absent. One names the boundary the *caller*
// supplies, so it sits outside the service rather than within it (DMN §10.4).
Members []string `json:"members,omitempty"`
// Internal is the subset of Members the service *encapsulates* — the decisions it
// evaluates on the way to its answer, as opposed to the output decisions it
// publishes (DMN §10.4 splits the two, and Members deliberately merges them
// because both are "made of").
//
// The difference is the whole point of a service. An output decision is what the
// service answers with, so calling it directly gets the same value by a longer
// route. An encapsulated one is a working, and a caller that addresses it has
// bound itself to an arrangement the service exists to be free to change — the
// interface is then a claim rather than a boundary. A caller cannot see which it
// is from the name, so this says it.
Internal []string `json:"internal,omitempty"`
}
DecisionInfo is a decision's self-description for the Modeler's decision picker (ADR-0050): the id to reference it by, its display name, the input data it consumes (so a business rule task's input mappings can be auto-filled), and its output (the process variable a result naturally lands in).
type DeployedDecision ¶
type DeployedDecision struct {
ID string
Name string
Model string
Inputs []DecisionField
Output DecisionField
// Service marks a decision service — the published interface over part of a
// model rather than one decision in it. A task calls either the same way.
Service bool
}
DeployedDecision is a decision available from a deployed model, described for the Modeler's decision picker (ADR-0050): its id, declared inputs and output, and the name of the model that provides it. It lets an author select — and auto-fill the inputs of — a decision that is deployed (and thus runnable) even when no separate DMN reference artifact exists for it.
type DirResolver ¶
type DirResolver struct {
Dir string
}
DirResolver resolves a handle against a directory of DMN files exported from temis: modelRef "risk-score" resolves to <Dir>/risk-score.dmn, falling back to <Dir>/risk-score.xml. It is the zero-config default source; a temis git or service resolver can replace it behind the Resolver interface without touching callers.
type Evaluation ¶
type Evaluation struct {
Outputs map[string]any
Trace []byte
// Key is the deployment whose model answered — a decision deployment, or the
// process's own key for a model bundled with it — so the evaluation record can
// say which version ran. 0 when the lookup that answered cannot name it (the
// runtime pointer of a definition deployed before ADR-0319).
Key uint64
}
Evaluation is what running a decision yields: its named outputs and, when the engine can produce one, the temis trace explaining how it got there — which tables ran and which rules fired (ADR-0066). Trace is canonical JSON or nil when no trace is available (a literal-expression decision, or a remote decision whose worker returns none).
type Evaluator ¶
type Evaluator func(ctx context.Context, decisionId string, inputs map[string]any) (Evaluation, error)
Evaluator evaluates a decision by id against an input context and returns its outputs and (when available) trace. It is the seam between a business rule task's I/O semantics and the engine that runs the decision: the local embedded temis library and a remote temis worker each provide one (ADR-0050).
type GraphEdge ¶
type GraphEdge struct {
Type string `json:"type"`
Source string `json:"source"`
Target string `json:"target"`
}
GraphEdge is one requirement, directed from the required (upstream) element to the one that requires it — matching the DMN arrow direction. Type is "informationRequirement" or "knowledgeRequirement".
type GraphNode ¶
type GraphNode struct {
ID string `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
DataType string `json:"dataType,omitempty"`
VarName string `json:"varName,omitempty"`
HasTable bool `json:"hasTable,omitempty"`
X float64 `json:"x,omitempty"`
Y float64 `json:"y,omitempty"`
Width float64 `json:"width,omitempty"`
Height float64 `json:"height,omitempty"`
}
GraphNode is one element of a DMN model's decision requirements graph, for a read-only viewer (ADR-0014's non-goal is a DMN *editor*; viewing what a referenced model contains is in scope). Type is "decision", "inputData", or "businessKnowledgeModel". X/Y/Width/Height carry the authored DMNDI bounds when the model has a diagram (all zero otherwise, so the client auto-lays-out).
type ModelGraph ¶
type ModelGraph struct {
Resolved bool `json:"resolved"`
Valid bool `json:"valid"`
ModelName string `json:"modelName,omitempty"`
Message string `json:"message,omitempty"`
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
}
ModelGraph is a referenced DMN model's requirements graph plus its resolve/valid status, so a viewer can render the diagram or show why it can't. Nodes/Edges are empty (never null) unless the model resolved and compiled cleanly.
type ProcessLookup ¶
type ProcessLookup func(defKey uint64) *compiler.CompiledProcess
ProcessLookup resolves a process-definition key to its compiled process. The worker uses it to find the decision, inputs, and result variable a business-rule job belongs to, so one handler serves every deployed process.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry holds the DMN models Atlas has deployed. It compiles each model once with temis and keeps the immutable result, keyed by the deployment it belongs to, ready for cheap repeated evaluation.
Two kinds of deployment put a model here, and the key space is the same one (the server's definition keys), so one map serves both:
- a **process deployment** bundles the models its business rule tasks need and registers them under the process-definition key (ADR-0014);
- a **decision deployment** is a decision published in its own right, durable and versioned, under a key of its own (ADR-0319).
A Registry is read and written concurrently, and says so rather than assuming otherwise. It is populated at runtime — every decision deploy registers a model on the run loop — while business rule tasks are evaluated off it, by job handlers running outside the loop (job.Runner.Work). Its indexes are therefore behind a read–write lock. A compiled model is immutable, so the lock covers the lookup and not the evaluation: a reader takes what it needs and evaluates after letting go.
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry creates an empty registry over a fresh temis engine.
func (*Registry) Deploy ¶
Deploy compiles a DMN model and registers it under the process-definition key of the process whose business rule tasks reference it. A process may bundle several models (its tasks can call decisions from different models), so Deploy appends — call it once per bundled model. Compilation happens here, at deploy time, never at evaluation time (invariant I5). It returns an error if temis cannot parse or compile the model.
It does *not* make this model the newest version of the decisions it provides: that is Registry.DeployDecision's job, and keeping the two apart is what stops deploying a process from silently becoming "a new version of the decision" (ADR-0319). The one index a bundled model still moves is the legacy runtime-latest pointer the definitions deployed before pinning read — see the Registry field comments.
func (*Registry) DeployDecision ¶ added in v0.7.0
DeployDecision compiles a DMN model published as a decision deployment — a decision in its own right rather than a model bundled with some process — and registers it under its own definition key (ADR-0319). It additionally makes that key the newest deployed version of every decision the model provides, which is what a latest-bound business rule task is pinned against when its process is deployed.
Like Deploy it is the deploy-time gate: a model temis cannot compile, or whose diagnostics carry errors, is refused and leaves nothing behind (invariant I5 — compilation happens here, never at evaluation time).
func (*Registry) DeployedDecisions ¶
func (r *Registry) DeployedDecisions() []DeployedDecision
DeployedDecisions describes every decision provided by the newest deployed model that supplies it (ADR-0063 latest binding), for the Modeler's picker. It reads the registry's already-compiled models — no XML parsing or resolver I/O — so it must run on the registry's owning goroutine (the run loop), the same single-writer discipline as Deploy. Results are sorted by decision id for a stable picker.
func (*Registry) Evaluate ¶
func (r *Registry) Evaluate(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, error)
Evaluate runs the named decision from the model deployed under defKey against the given input context and returns its outputs (decision name → value). It is the runtime hot spot of the integration, but it runs on a worker, off the processor goroutine.
func (*Registry) EvaluateLatest ¶
func (r *Registry) EvaluateLatest(ctx context.Context, decisionId string, in map[string]any) (map[string]any, error)
EvaluateLatest runs the named decision from the newest deployed model that provides it (ADR-0063), for a latest-bound business rule task. It is otherwise identical to Evaluate; only the model selection differs.
func (*Registry) EvaluateLatestTraced ¶
func (r *Registry) EvaluateLatestTraced(ctx context.Context, decisionId string, in map[string]any) (map[string]any, []byte, error)
EvaluateLatestTraced is EvaluateLatest plus the temis trace (see EvaluateTraced).
func (*Registry) EvaluateTraced ¶
func (r *Registry) EvaluateTraced(ctx context.Context, defKey uint64, decisionId string, in map[string]any) (map[string]any, []byte, error)
EvaluateTraced is Evaluate plus the temis trace explaining how the decision was made — which tables ran, which rules matched, and why (ADR-0066). The trace is canonical JSON (temis's tdmn.Trace tree) or nil for a decision with no table logic (a literal expression). The DMN worker uses it to retain a debuggable record of the evaluation. Tracing runs off the processor goroutine, so its extra allocation is not on any hot path (temis's WithTrace, ADR-0013/WP-51).
func (*Registry) Graph ¶ added in v0.7.0
func (r *Registry) Graph(defKey uint64, decisionId string) (ModelGraph, bool)
Graph returns the requirements graph of the model that the deployment under defKey evaluates decisionId from — the picture of the decision as the evaluation saw it, not as the design-time model reads today. It is the graph frozen at registration, so this is a map lookup: it runs on the run loop, where compiling anything is out of the question (invariants I1/I5).
defKey 0, or a key whose deployment is gone, falls back to the newest model providing the decision. That is the same fallback a latest-bound task evaluates under, and it is honest about what it can still answer: a process definition deleted after its instances ran leaves evaluations behind whose model is no longer addressable any other way. The second return is false when nothing on this server provides the decision at all.
func (*Registry) IsService ¶ added in v0.7.0
IsService reports whether the name addresses a decision *service* — DMN's published interface over part of the graph — rather than one decision in it, in the model the deployment under defKey resolves it from. It answers one question a reader of an evaluation cannot otherwise settle: a service evaluation retains no rule trace (ADR-0398), and "no rules were recorded" and "no rules ran" are not the same thing to say to somebody asking how a case was decided.
func (*Registry) LatestDecisionIDs ¶ added in v0.7.0
LatestDecisionIDs returns the set of decision ids that have a decision deployment — the ones a latest-bound reference resolves to something other than the model bundled with its own process (ADR-0327).
The deploy-time gate reads it to tell a business rule task that can evaluate from one that cannot: a latest-bound reference in this set needs no model bundled, because pinDecisions will resolve it to that deployment's key. It reads registry state, so it runs on the registry's owning goroutine — the run loop — and the caller hands the answer to the off-loop preflight.
func (*Registry) LatestDecisionKey ¶ added in v0.7.0
LatestDecisionKey returns the key of the newest decision deployment providing the decision id, and ok=false when the decision has never been deployed on its own. It is the deploy-time selector behind latest binding: a process deployment resolves each latest-bound reference through it once and stores the answer, so nothing is re-decided at task activation or on replay (invariants I5/I6).
It reads registry state, so it runs on the registry's owning goroutine — the run loop — the same discipline as Deploy.
func (*Registry) Provides ¶ added in v0.8.0
Provides reports whether a model registered under the key provides the decision — for a process deployment, whether the process carries its own copy of it.
func (*Registry) Reload ¶ added in v0.4.0
Reload is Deploy for a model that is *already* deployed — one snapshotted into a deployment record, coming back at startup — and so it does not re-apply the deploy-time gate above (ADR-0177). Refusing the model here would not undeploy anything; it would only keep the server from starting, with every other definition and every running instance behind it, because a diagnostic that did not exist when the model was deployed exists now.
The model is registered and the error diagnostics are returned rendered for display ("" when there are none), for the caller to report. Rendered, not structured, because temis documents diagnostic messages as human-readable and explicitly not a stable API: they are for an operator to read, not for code to act on.
This is safe because of what temis guarantees about a diagnostic: malformed XML is a hard error, but per-decision problems leave the rest of the model compiled, and a decision whose logic failed to compile is "present but not executable" — so evaluating *that* decision fails the way a failing decision has always failed, as a job error on a worker, while every other decision in the model still answers. A hard compile error still returns an error here: there is no model to bring back.
func (*Registry) ReloadDecision ¶ added in v0.7.0
ReloadDecision is DeployDecision for a decision that is *already* deployed — one coming back from its durable record at startup — and so it does not re-apply the deploy-time gate, for the reason Registry.Reload gives (ADR-0177): refusing it here undeploys nothing, it only keeps the server from starting. The rendered error diagnostics are returned ("" when there are none) for the caller to report.
func (*Registry) UndeployDecision ¶ added in v0.7.0
UndeployDecision removes a decision deployment from the registry and leaves the two "newest model providing this decision" pointers exactly as a restart would build them — which is the only definition of correct available here (ADR-0336).
Removing the key is the easy half. The hard half is that both pointers are last-write-wins: each holds *the newest* provider and keeps no history, so a pointer aimed at the removed key cannot simply be cleared — it has to fall back to the next-newest, and nothing here remembers which that was.
It is rebuilt rather than remembered. Keys come from one monotonic counter, so ascending key order *is* registration order, live and on recovery alike; walking the survivors in that order reproduces exactly the state the same records would produce after a restart. That is what makes a delete survivable: what the server answers now and what it answers after rebooting cannot diverge.
Removing a key that is not a decision deployment — a process's bundled model, or nothing at all — is a no-op, so an unknown key is not an error here.
type Resolver ¶
Resolver turns a DMN reference handle — the modelRef an Atlas project stores (ADR-0034) — into the DMN model XML authored in temis. It is the seam between "this project references decision X" and "here is X's model": a filesystem folder of temis-exported models today, a temis git repo or service later. Implementations must be safe for concurrent use.
type Result ¶
type Result struct {
ElementInstanceKey uint64
ProcessDefKey uint64
DecisionId string
Inputs map[string]any
Outputs map[string]any
Trace []byte // temis trace JSON explaining the evaluation; nil if none
}
Result is one evaluated business rule task's outcome, delivered to the optional sink a Handler is built with. The decision's outputs are written back into the instance as process variables (see Handler), and the full evaluation (inputs, outputs, trace) is retained as a durable debugging record (ADR-0066); the sink is an additional observation seam for tests and diagnostics, not the primary path.
type ServiceResolver ¶
type ServiceResolver struct {
// BaseURL is the model source root; the handle "risk-score" resolves to
// <BaseURL>/risk-score.dmn.
BaseURL string
// Client is the HTTP client to use; nil uses a client bounded by the shared
// worker call budget (nettimeout.Default), never an unbounded one — this
// resolver is called from the DMN worker on the run-loop goroutine.
Client *http.Client
// Token, if set, is sent as an "Authorization: Bearer <Token>" header — the
// credential for a private temis service or git host.
Token string
}
ServiceResolver resolves a DMN reference handle against a temis model source reachable over HTTP: it GETs <BaseURL>/<modelRef>.dmn and returns the body. It is the networked alternative to DirResolver behind the same Resolver interface — a temis git host (raw file URLs) or a temis model service both fit this shape, so which source Atlas reads from is a deployment choice, not a code change (ADR-0034/0014).
A 404 is reported as ErrNotFound (an unresolved, user-fixable reference); any other non-2xx response or a transport failure is returned as an infrastructure error, so a caller can tell "no such model" from "the model source is broken". A ServiceResolver is safe for concurrent use.
func (ServiceResolver) Resolve ¶
Resolve fetches the model file for a handle from the service. A missing model (404) yields ErrNotFound; any other non-2xx status or a transport error is returned as-is so the caller reports it as an infrastructure failure, not an unresolved reference. The handle is validated exactly as DirResolver validates it, so it can never escape BaseURL's path.
type Standalone ¶ added in v0.20.0
type Standalone struct {
// contains filtered or unexported fields
}
Standalone is one compiled decision held outside the registry, by a caller that owns its lifecycle: an authorization policy embeds a decision table and is compiled when the policy is published (ADR-0475 §6).
It is not a deployment. Nothing is keyed or registered, so publishing a policy can never become "a new version" of a decision a process is bound to (ADR-0319), and the one temis engine stays behind this package, as FEEL stays behind expr (ADR-0015).
A compiled model is immutable, so a Standalone is safe for concurrent evaluation.
func CompileStandalone ¶ added in v0.20.0
CompileStandalone compiles src once and checks that it provides decisionID. A model with errors, or without that decision, is refused here rather than at the first evaluation: publication is where an author can still fix it (invariant I5).
func (*Standalone) Evaluate ¶ added in v0.20.0
Evaluate runs the decision over in and returns the decision's own result: a context (map) for a table with several outputs, the bare value for one with a single output. It is the registry's evaluation path — aliased input names, the type check that refuses a mismatched input instead of letting every rule quietly miss, and numbers restored from their decimal strings — so a decision answers here exactly as it would on a business rule task.
type Trial ¶ added in v0.7.0
type Trial struct {
OK bool `json:"ok"`
Message string `json:"message,omitempty"`
// What the model offers, whenever it compiled: the panel's form is built from
// this, so the fields are temis's own view of the model rather than something a
// client re-derived from the XML.
ModelName string `json:"modelName,omitempty"`
Decisions []DecisionInfo `json:"decisions"`
// What one decision produced. Present only when a decision was named and ran.
DecisionID string `json:"decisionId,omitempty"`
Outputs map[string]any `json:"outputs,omitempty"`
Trace json.RawMessage `json:"trace,omitempty"`
}
Trial is the answer to "what does this model do with these inputs": what it can run and what that wants, and — when a decision was named — what it produced.
OK is false for a model that does not compile, a decision id the model does not provide, and an evaluation that errored, with Message saying which. Those are the normal output of authoring rather than a caller's mistake, so they come back as a result to render, not as an HTTP error (the grammar POST /api/v1/feel/evaluate already uses).
type ValidationResult ¶
type ValidationResult struct {
Resolved bool // the modelRef resolved to an actual model
Valid bool // the resolved model compiled without errors in temis
ModelName string // the DMN <definitions name>, when resolved
Decisions []string // decision names the model exposes, when valid
// Aliases lists the further names those decisions answer to — a decision's FEEL
// identifier (its <variable name>) wherever it differs from the name above.
// Matching a business rule task's decisionId reads both; a listing and a
// deployment record read Decisions alone, so a decision is published under one
// name and one only (names.go).
Aliases []string
// Services lists the decision services the model publishes — DMN's interface
// over part of a DRD, which a business rule task addresses exactly as it
// addresses a decision (services.go).
Services []string
Message string // human-readable reason when unresolved or invalid
}
ValidationResult reports the outcome of resolving a DMN reference and checking it against temis (ADR-0034 Phase 2). It is a pure preflight result: no engine or registry state is mutated by producing it.
type Validator ¶
type Validator struct {
// contains filtered or unexported fields
}
Validator resolves DMN references and validates them against temis. It owns a temis engine used only to compile (nothing is deployed) and a Resolver for fetching model XML. Safe for concurrent use once constructed.
func NewValidator ¶
NewValidator builds a Validator over a resolver and a fresh temis engine.
func (*Validator) Describe ¶
Describe resolves modelRef, compiles it, and returns its model name and the self-description of everything the model offers a caller by name — its decision services and its evaluable decisions, each with its inputs and output. Like Validate it returns a non-nil error only for an infrastructure failure; an unresolved handle or an invalid model yields an empty result (a best-effort catalog entry), not an error, so one broken reference does not blank the whole picker.
func (*Validator) Graph ¶
Graph resolves modelRef, compiles it, and returns its decision requirements graph for a read-only viewer. Like Validate it returns a non-nil error only for an infrastructure failure; an unresolved handle or an invalid model is a normal result carrying a Message (and no nodes), so the viewer can explain the state instead of erroring.
Every node comes back with bounds: a model whose diagram does not cover it is completed on the way through, so the viewer never has to invent a layout of its own (ADR-0325).
func (*Validator) Try ¶ added in v0.7.0
func (v *Validator) Try(ctx context.Context, src []byte, decisionID string, inputs map[string]any) Trial
Try compiles src with the validator's own temis engine — the one that exists to compile without deploying — and, when decisionID names a decision the model provides, evaluates it over inputs and returns its outputs with the trace.
With decisionID empty it evaluates nothing and only describes the model. That is one question asked twice over, not two modes: the panel needs to know what it may run and what that wants before it can ask for a run, and both answers come out of the same compile.
inputs carries decoded JSON, the same value shapes a business rule task's static inputs arrive as, so a decision tried here sees what it would see at runtime.
func (*Validator) Validate ¶
Validate resolves modelRef and compiles it with temis, reporting whether it resolved and whether it is a valid DMN model — the check a deploy runs before trusting a reference. It returns a non-nil error ONLY for an infrastructure failure (e.g. the model source is unreadable); an unresolved handle or an invalid model is a normal, reportable result, not an error, so a caller can surface it to the user rather than as a 500.
func (*Validator) ValidateXML ¶
func (v *Validator) ValidateXML(ctx context.Context, xml []byte) ValidationResult
ValidateXML compiles a DMN model already in hand (not resolved by a handle) and reports whether it is valid, with its name and decisions — the check the upload path runs before storing a model. Resolved is always true (the bytes are present), and no infrastructure failure is possible, so it returns no error.