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.2 Semantic authority

Quarto-Needs follows one architectural rule deliberately:

All engineering semantics are resolved by the core. Presentation layers consume semantic projections and must not redefine engineering meaning.

The relation catalog therefore owns not only relation names and labels, but also semantic family, endpoint roles, impact direction, and hierarchy/traversal direction. Public graph projections carry this safe semantic metadata alongside nodes and edges. Lua, Cytoscape, Mermaid adapters, and future renderers use it instead of maintaining independent tables that could drift from the core.

This distinction leaves presentation policy free to evolve. A browser may choose a different layout, spacing, color scheme, or interaction model; it may not independently decide that derives-from means a different parent/child direction.

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.