Definition & Catalogue ModelΒΆ
Every runtime resource is created from a
ResourceDefinition. This page describes the definition side of the resource model: where definitions come from (the two provisioning sources), the content-authoring taxonomy that supplies exam content, the catalogue / config plane of static reference data, and the authorization policy model that guards both. See ADR-051, ADR-052 and ADR-053.
Two provisioning sourcesΒΆ
A definition's provisioning_source is the single switch that decides whether it reconciles:
| Source | Origin | Lifecycle | Examples |
|---|---|---|---|
seed |
YAML assets loaded once by a seeder when the store is empty (the inverse of an export). | None β static config, immutable until re-seeded. No sync_status. |
SessionDefinition, PodDefinition, HostDefinition, DeliveryEnvironment, LabLocation, HostingSiteLocation, AuthorizationPolicy. |
content_package |
Synced from a PAv1 content package when content is published/updated. | Asymmetric β definition_status (draft β published β deprecated) + a sync_status reconciled against the package. |
The Form (the synced leaf of Track β β¦ β Form β FormItem) plus its per-part JobDefinition, ReportDefinition. |
The model's only asymmetry.
seeddefinitions are inert reference data: aResourceDefinitionwithdefinition_status = publishedand no reconciliation.content_packagedefinitions are synced (not provisioned). Within the taxonomy, theFormis the one node that actually reconciles β theform-controllerowns its sync loop (ADR-059); the surroundingTrack/Exam/Module/Formsetnodes are inert catalogue metadata.
stateDiagram-v2
[*] --> Draft : created (synced from package)
Draft --> Published : publish (content validated)
Published --> Deprecated : superseded by newer version
Published --> Published : re-sync (sync_status updated)
Deprecated --> [*]
note right of Published
Only content_package definitions
have this lifecycle. seed definitions
are static config (no lifecycle).
end note
Content-authoring taxonomyΒΆ
Exam content is authored as a strict, versioned taxonomy (ported from the legacy Track Manager
domain). Every node is a content_package ResourceDefinition; ids are deterministic slugs
derived from the parent chain, which makes re-sync idempotent (a duplicate id is a conflict, not a
new node).
erDiagram
Track ||--o{ TrackItem : "canonical items"
Track ||--o{ Exam : "versions"
Exam ||--o| Blueprint : "active revision"
Blueprint ||--o{ BlueprintNode : "topic tree"
BlueprintNode ||--o{ BlueprintNode : "parent of"
Exam ||--o{ Module : "split into"
Module ||--o{ Formset : "groups"
Formset ||--o{ Form : "versions"
Form ||--o{ FormItem : "composed of"
Form ||--o| PodDefinition : "optional pod (ADR-059)"
TrackItem ||--o| FormItem : "realized by"
FormItem }o--o{ BlueprintNode : "tagged with"
SessionPart }o--|| Form : "delivers (by FQN)"
Track }o--o| AuthorizationPolicy : "guarded by"
Exam }o--o| AuthorizationPolicy : "guarded by"
Form }o--o| AuthorizationPolicy : "guarded by"
AuthorizationPolicy ||--o{ AuthorizationRequirement : "contains"
| Node | What it is | Identity |
|---|---|---|
| Track | A certification line (e.g. Exam CCIE DEMO). Owns the canonical item catalogue and one or more versioned exams. | slug("{Type} {Level} {Acronym}") |
| TrackItem | A canonical, auto-numbered catalogue entry (the stable task/question a form item realises). | slug("{track} {sequence}") |
| Exam | A versioned realisation of a track with a publication lifecycle (Draft β Released β Retired). |
slug("{track} v{version}") |
| Blueprint / BlueprintNode | The weighted topic taxonomy an exam is measured against (a revisioned, self-referential tree). | "{exam}-{revision}" / "{blueprint}-{guid}" |
| Module | A section of an exam (e.g. a lab or written section). | slug("{exam} {acronym}") |
| Formset | A versioned family of forms (e.g. DOO-1.x). | slug("{module} {version}.x") |
| Form | A concrete deliverable version (e.g. DOO 1.1) β the single synced content_package resource (ADR-059): owns sync_status, synced content bytes, and an optional PodDefinition ref. Generalises the legacy LabletDefinition. Delivered by a SessionPart. |
slug("{formset trimmed}{version}") |
| FormItem | An item as placed on a form: references its TrackItem, carries placement + type-driven scoring, tagged with BlueprintNodes. |
GUID |
Form qualified name (FQN). A Form is addressed across services by its FQN β the six-token
path through the taxonomy ({Type} {Level} {Acronym} v{version} {module} {form}). This is the key a
PartDefinition selects by pattern.
Form delivery β the synced unit, no timed instanceΒΆ
A Form is authored content that syncs (ADR-059): it is the
single content_package resource that reconciles (owns sync_status + the RustFS bytes) and
carries an optional PodDefinition ref β a self-contained content + optional pod deliverable.
It generalises the legacy LabletDefinition (single-part) to every use-case.
It is delivered by a SessionPart (the timed runtime instance) which selects the Form by
FQN and inherits its optional pod. The Form lives on the catalogue / sync plane, so there is
no separate timed Form instance in the runtime tree β the part is the runtime that delivers
the form. Per-part automation (JobDefinition, ReportDefinition) travels with the same content
package and drives the part's workflow lifecycle phases (collect / grade / report).
Catalogue / config planeΒΆ
Reference data that has no timeslot and no reconciliation is modelled as seed
ResourceDefinitions in a uniform catalogue. They are configuration the runtime reads, never
resources the runtime drives:
| Definition | Purpose |
|---|---|
DeliveryEnvironment |
Which session types are deliverable in a given environment. |
LabLocation |
A logical lab location (capacity / placement target). |
HostingSiteLocation |
A physical hosting site / rack a host belongs to. |
AuthorizationPolicy |
A reusable, named guard (see below) referenced by any definition or resource. |
Deferred β Device / DeviceDefinition
A managed Device instance and its DeviceDefinition are out of scope this round
(ADR-050 Β§2.3). Device-based parts currently reach pre-existing devices through
ROC / RADkit and provision nothing β see session-model.md and the
glossary. They will be added later as a ResourceDefinition / TimedResource
pair, with no change to the model.
Authorization modelΒΆ
Per-resource authorization is ported from the legacy domains as first-class definitions
(AuthorizationPolicy + embedded AuthorizationRequirements). Keycloak/OIDC remains the
authentication layer; the policy model adds fine-grained authorization filtering on top of it.
- An
AuthorizationPolicyis a named, seed-provisionedResourceDefinitioncomposed of requirements. Any definition or resource may reference one viaauthorization_policy_id. - An
AuthorizationRequirementis one of two shapes: - Claim β a
claim_type(regex-capable) + optionalclaim_value. The value may be a JQ runtime expression evaluated against the entity under check (e.g. "the user'strackclaim must equal this track's id"). Absence β existence check only. - Composite β
AllorAnyover nested requirements (recursive).
Evaluation filters results rather than hard-failing, and admin principals bypass all checks
(mirrors the legacy β¦ForCurrentUser queries):
flowchart TB
U[ClaimsPrincipal] --> A{IsAdmin?}
A -- yes --> G[Grant / include]
A -- no --> P{Resource has authorization_policy_id?}
P -- no --> G
P -- yes --> L[Load AuthorizationPolicy]
L --> R{Evaluate each requirement}
R --> T{Requirement type}
T -- Claim --> CV["Resolve claim value (JQ runtime expr, entity params)"]
CV --> CM{User has matching claim?}
T -- Composite --> CC{Condition}
CC -- All --> ALL[every child passes]
CC -- Any --> ANY[at least one child passes]
CM -- yes --> G
CM -- no --> D[Deny / filter out]
ALL --> G
ANY --> G
PersistenceΒΆ
Like every aggregate in LCM, definitions are state-based: a Neuroglia AggregateRoot whose
state is persisted by a MotorRepository with state_version (not EventStore). The legacy
event-sourced behaviour is preserved as @dispatch reducers over domain events; only the
persistence differs. See ADR-051.
Where definitions are reconciledΒΆ
Target topology (ADR-054 Rev 2, Proposed)
The controller names below (form-controller, pod-controller, host-controller) describe the
per-resource-kind target topology proposed in
ADR-054, not the current
as-built services. Today the same responsibilities live in control-plane-api (catalogue
+ content sync), lablet-controller (session / part / pod reconcile) and
worker-controller (host / license). Read the Owner column as "the controller that owns
this kind once ADR-054 lands."
| Definition family | Source | Owner (target β ADR-054 Rev 2) |
|---|---|---|
Form (synced leaf) + per-part JobDefinition / ReportDefinition |
content_package | form-controller (sync loop β ADR-059) |
Track β¦ Formset, FormItem, Blueprint (inert taxonomy) |
content_package | form-controller (seeded with the package), consumed by CPA |
SessionDefinition, PartDefinition |
seed | CPA (catalogue seeder) |
PodDefinition |
seed / content_package | pod-controller (consumes; pod ref resolved from the Form) |
HostDefinition |
seed | host-controller |
Catalogue / config + AuthorizationPolicy |
seed | CPA (seeder) |
See ADR-054 for the controller topology.
RelatedΒΆ
- resource-model.md β the instance side (ResourceInstance / TimedResource).
- session-model.md β how a
SessionPartselects and delivers aForm. - unified-resource-management.md β resource lifecycle vs job automation.