Help us shape the future of Go! Take the 2026 Go Developer Survey

decisiondoc

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: AGPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package decisiondoc serves decision documentation (ADR-0324): a published DMN decision as a stored PDF plus the prose and rule tables it describes, its version history, and the revocable public link a reader without an account follows.

It is a deliberate sibling of api/processdoc, which does the same for a BPMN process, and the two are meant to be diffed against each other. The duplication is recorded in the ADR together with the rule for undoing it: when a third artifact kind wants a document, extract the generic half first and add the third second. Generalising now would mean either migrating a landed store's records or keeping a field whose name is a lie.

The single-writer boundary is the runloop.Loop this service holds: every read and write of the store goes through it, and there is no other way from here to shared state (I3, ADR-0002). Design-time only — nothing here reaches the event log, the processor, or recovery.

Index

Constants

View Source
const PublicPath = "/public/decision-docs/"

PublicPath is the prefix of the unauthenticated share URL. It lives in one place so the minted URL and the served route cannot drift.

Variables

This section is empty.

Functions

func NewID

func NewID() (string, error)

NewID mints a version id. 16 bytes of crypto randomness hex-encoded is filename-safe (so the id is its own store key) and collision-free in practice.

Types

type Column

type Column struct {
	// Label is the column heading a modeller gave it, empty when unnamed.
	Label string `json:"label,omitempty"`
	// Expression is what an input column evaluates, or an output column's variable
	// name. It is the thing a reader has to match a value against.
	Expression string `json:"expression,omitempty"`
	// Type is the declared typeRef, empty when the column declares none.
	Type string `json:"type,omitempty"`
}

Column is one column of a decision table, input or output, as the document reproduces it: what it reads (an input's expression, an output's name) and the type it declares.

type Decision

type Decision struct {
	ID   string `json:"id"`
	Name string `json:"name,omitempty"`
	// Description is the decision's <description> text.
	Description string `json:"description,omitempty"`
	// Inputs are the input data this decision consumes, by name and declared type.
	Inputs []Column `json:"inputs,omitempty"`
	// HitPolicy is the table's policy ("UNIQUE", "COLLECT" …), with its aggregation
	// where it has one. Empty for a decision whose logic is not a table.
	HitPolicy string `json:"hitPolicy,omitempty"`
	// InputColumns and OutputColumns describe the table's shape; Rules are its rows.
	InputColumns  []Column `json:"inputColumns,omitempty"`
	OutputColumns []Column `json:"outputColumns,omitempty"`
	Rules         []Rule   `json:"rules,omitempty"`
	// Literal is the FEEL expression of a decision whose logic is a literal
	// expression rather than a table — for those, this is the whole of the logic.
	Literal string `json:"literal,omitempty"`
}

Decision is one decision of the model as it was documented: the prose a reader needs about it and the logic it runs, snapshotted at publish time so a later edit to the model cannot rewrite what an already-published version says.

type Doc

type Doc struct {
	ID string `json:"id"`
	// DecisionID is the DMN decision id the document describes. It binds to the id,
	// not a deployment, because a decision is documented whether or not it is
	// deployed — and a business rule is usually signed off before it runs, which is
	// the case this version line exists for.
	DecisionID string `json:"decisionId"`
	// ModelName is the DMN <definitions name>, and ModelRef the handle the decision
	// is stored under when it is in the model at all.
	ModelName string `json:"modelName,omitempty"`
	ModelRef  string `json:"modelRef,omitempty"`
	// Version is a per-decisionId counter, 1-based. It answers "which documented
	// state of this decision is that?", never "which deployment" — the deployment
	// version (ADR-0319) answers a different question and is deliberately separate.
	Version   int32  `json:"version"`
	Title     string `json:"title,omitempty"`
	Note      string `json:"note,omitempty"`
	CreatedAt int64  `json:"createdAt"`
	CreatedBy string `json:"createdBy,omitempty"`
	PDFSize   int64  `json:"pdfSize"`
	// Decisions is the documented decision prose and logic, snapshotted by value.
	Decisions []Decision `json:"decisions,omitempty"`
	// XML is the DMN source the document was produced from, so a reader of the
	// history can recover the exact model a version describes.
	XML string `json:"xml,omitempty"`
	// ShareToken is the opaque handle that serves this version's PDF without a
	// login. Empty means unshared, which is the default — the artifact leaves the
	// system only when someone says so.
	ShareToken string `json:"shareToken,omitempty"`
}

Doc is one published documentation version of a decision: immutable metadata describing what was documented, with the PDF stored beside it.

type Rule

type Rule struct {
	// Inputs holds one entry per input column, in order. An empty entry is DMN's
	// "this column does not constrain this rule".
	Inputs []string `json:"inputs"`
	// Outputs holds one entry per output column, in order.
	Outputs []string `json:"outputs"`
	// Description is the rule's own annotation, which is where a modeller writes
	// why a rule exists.
	Description string `json:"description,omitempty"`
}

Rule is one row of a decision table: the unary tests in its input cells and the values in its output cells, in column order, plus whatever was written about it.

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.
	Limits limits.Limits
	// contains filtered or unexported fields
}

Service serves the decision-documentation area. Build it with New.

func New

func New(loop *runloop.Loop, store *Store, allow func(clientIP string) bool,
	newToken func() (string, error)) *Service

New builds the documentation service over its own store directory. allow and newToken are the collaborators the server supplies; neither may touch state the loop owns.

Unlike its process sibling it takes no deployment lookup. A decision document is a sign-off artifact and is usually published *before* the decision is deployed, so the field would be empty in the common case; ADR-0324 leaves it out until somebody asks for it.

func (*Service) HandleCreate

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

HandleCreate records the next documentation version of a decision. Body: the produced PDF plus the decision prose and rule tables it describes.

func (*Service) HandleDelete

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

HandleDelete prunes a version, taking its public URL with it. A missing version is not an error, so pruning is idempotent.

func (*Service) HandleGet

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

HandleGet returns one version in full: its metadata, the documented decisions, and the DMN source it was produced from.

func (*Service) HandleGetPDF

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

HandleGetPDF downloads a version's document.

func (*Service) HandleList

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

HandleList returns one decision's documentation history, newest version first.

func (*Service) HandlePrune

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

HandlePrune applies a retention limit to a decision's documentation history: it keeps the newest `keep` versions and deletes the older ones, PDF and all. Idempotent — pruning an already-short history removes nothing.

func (*Service) HandlePublic

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

HandlePublic serves a shared version's PDF to a reader with no account. An unknown, malformed, or revoked token is one indistinguishable 404: the response must not reveal whether a document ever existed behind it.

func (*Service) HandleShare

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

HandleShare mints (or returns the existing) public link for one version. Idempotent by design: re-sharing must not rotate a URL that readers already hold.

func (*Service) HandleUnshare

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

HandleUnshare revokes a version's public link, killing the URL.

func (*Service) LoadVersions

func (s *Service) LoadVersions() error

LoadVersions rebuilds the per-decision documentation counter from the durable records, so an export after a restart continues the sequence rather than restarting it at v1 and overwriting history. It runs before the loop serves traffic, so touching the map directly here respects the single-writer invariant.

type Store

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

Store is a durable store for decision documentation versions: one JSON record per version id under a single directory, each beside the PDF it describes. The PDF is why it wraps the shared store rather than being one — a version is two files, and both have to go together.

Every entry point rejects an id that is not bare hex before touching the filesystem. Ids here name their own files directly, so that check is what keeps an id from escaping the directory.

func NewStore

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

NewStore opens (creating if needed) the decision-docs directory. Versions list grouped by decision, newest version first within each, tie-broken by id so the order is deterministic.

func (*Store) ByShareToken

func (s *Store) ByShareToken(shareToken string) (Doc, bool, error)

ByShareToken finds the version a public token addresses. An empty or unsafe token never matches, so the many unshared records — which carry no token — stay private.

func (*Store) Delete

func (s *Store) Delete(id string) error

Delete removes a version and its document. A missing file is not an error, so cleanup is idempotent; the PDF goes with the record, since neither is meaningful without the other. It shadows the embedded store's Delete so no caller can remove a record and leave its document orphaned.

func (*Store) ForDecision

func (s *Store) ForDecision(decisionID string) ([]Doc, error)

ForDecision returns one decision's documentation history, newest version first.

func (*Store) Get

func (s *Store) Get(id string) (Doc, bool, error)

Get returns a version's record, or ok=false if there is none. An unsafe id is a clean miss rather than a filesystem lookup. It shadows the embedded store's Get deliberately: every caller goes through the guard.

func (*Store) PDF

func (s *Store) PDF(id string) ([]byte, error)

PDF returns a version's document bytes. An unknown or unsafe id is an error rather than an empty document, so a handler cannot serve a zero-byte PDF as if it were real.

func (*Store) PruneDecision

func (s *Store) PruneDecision(decisionID string, keep int) ([]string, error)

PruneDecision enforces a retention limit on one decision's documentation history: it keeps the newest `keep` versions and deletes the rest, returning the ids it removed. Every version keeps a PDF, so an unpruned archive grows without limit.

keep is clamped at zero — a negative limit would otherwise delete history it was asked to retain. keep >= the number of versions prunes nothing. Deleting the oldest is deliberate: history is answered newest-first, and the reason to prune is that ancient versions are the ones no longer worth their bytes.

func (*Store) Save

func (s *Store) Save(rec Doc, pdf []byte) error

Save writes a version durably: the PDF first, then the record. The order matters — a record is the thing readers discover, so it must never point at a document that is not yet on disk (I2).

func (*Store) SaveRecord

func (s *Store) SaveRecord(rec Doc) error

SaveRecord rewrites only the metadata sidecar, leaving the stored PDF alone. Minting or revoking a share token is such an update: the document is immutable, who may read it is not.

Jump to

Keyboard shortcuts

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