3 Engineering model
3.1 Objects, relations, and graph
The canonical model is deliberately generic. Every traceable item is an engineering object; relations are typed edges; together they form a property graph independent of the renderer.
QMD → DeclarationBatch → AnalysisSnapshot → exports / Quarto views
This separation matters: Python owns semantics and Lua reads only already-resolved projections. HTML, PDF, DOCX, the CLI, and exporters therefore start from the same analyzed state.
3.3 Types as policy
Types can declare required attributes, a semantic role, allowed states, and an ID prefix.
[types.non-functional-requirement]
id-prefix = "NFR-"
role = "requirement"
allowed-statuses = ["draft", "in-review", "approved", "deprecated"]
required-attributes = ["priority", "tags"]
[types.architecture-decision]
id-prefix = "ADR-"
role = "decision"
allowed-statuses = ["proposed", "accepted", "rejected", "deprecated", "superseded"]
required-attributes = ["date", "decision-makers", "tags"]id-prefix is optional policy. There is no obligation to repeat the domain in every identifier. In a book entirely dedicated to IAM, ADR-001 usually carries more signal than IAM-ADR-001. In a multi-domain monorepo, IAM-ADR-001 may be useful; simply configure that prefix.
3.4 Semantic roles
Roles avoid inferring meaning from fragile naming conventions. Examples:
| Type | Role |
|---|---|
functional-requirement |
requirement |
architecture-decision |
decision |
component |
architecture-element |
test-case |
verification |
evidence |
evidence |
risk |
risk |
The type name remains part of the project domain; the role provides a more stable semantic layer for rules and integrations.
3.5 Query-backed graph views
Named queries are evaluated only by Python and materialized as ordered ID sets. Graph generation now reuses those same selections to publish bounded graph projections under .quarto-needs/graphs/, plus a small views.json manifest.
That makes a query reusable across tables, dashboards, CLI analysis, and graph exploration without duplicating its predicate:
[queries.accepted-decisions]
all = [
{ field = "type", op = "eq", value = "architecture-decision" },
{ field = "status", op = "eq", value = "accepted" },
]
sort = ["id:asc"]{{< need-table query="accepted-decisions" >}}
{{< need-graph query="accepted-decisions" >}}If a query-backed graph exceeds the configured node/edge budget, the engineering analysis remains valid. The view manifest records that presentation as unavailable and the shortcode reports the reason rather than silently truncating the graph.
3.6 Baselines and identity
IDs are identity, not titles. Moving an object between files does not change its identity; changing the ID is a removal plus an addition. Baselines store fingerprints of content, relations, and configuration, allowing the engine to distinguish content changes, relocation, and semantic relation changes.