ADR-059: Form as a First-Class Synced Resourceยถ
| Attribute | Value |
|---|---|
| Status | Proposed |
| Date | 2026-06-16 |
| Deciders | Architecture Team |
| Extends | ADR-050 (Definition/Instance Duality), ADR-051 (Provisioning Sources), ADR-052 (Content-Authoring Taxonomy) |
| Supersedes | the "Form delivery โ no synced Form, inert taxonomy leaf" stance of ADR-052 ยงForm-delivery and the Form row of the ADR-050 ยง2.3 catalogueโruntime map; generalises ADR-028 (LabletDefinition PENDING_SYNC) |
| Related ADRs | ADR-045 (Multi-part Session/Part), ADR-046 (Pod/Host split), ADR-054 (Controller topology โ form-controller) |
1. Contextยถ
The platform must now deliver four use-cases, not one: lablet, practicelab,
expertlab (CCIE, 2 parts), and expertdesign (CCDE, 4 parts). The current model carries the
Lablet use-case through a LabletDefinition โ a catalogue entry that, uniquely, owns a
sync lifecycle (PENDING_SYNC โ SYNCED, ADR-028): it pulls content from Mosaic, lands it in
RustFS, and fans out to LDS + SE. Everything else in the content-authoring taxonomy
(Track โ Exam โ Module โ Formset โ Form โ FormItem, ADR-052) is modelled as inert
content_package metadata, and a Form was declared "delivered by SessionPart โ no
instance".
That framing has two problems as we generalise:
- The synced unit is mis-named. What actually syncs (owns
sync_status, RustFS bytes, and the LDS/SE fan-out) is the per-deliverable content unit โ today the single-partLabletDefinition. In the generalized taxonomy that unit is theForm. Keeping a separateLabletDefinition"profile" beside an inertFormduplicates the concept and only works for single-part deliveries. - The pod binding lives in the wrong place. A pod is intrinsic to the content a part runs
(a
cml_on_awslab, aproxmoxtopology, or none for a web/design form), yetpod_refcurrently hangs offPartDefinition. A multi-part exam that reuses the sameFormin different slots would have to re-declare the pod per part.
We want one synced unit, reused across every use-case, that a SessionPart simply references.
2. Decisionยถ
2.1 The Form is the synced content resourceยถ
Generalise the legacy LabletDefinition into the Form: a first-class synced
content_package ResourceDefinition. The Form is the single reconciled (synced) unit of
the content-authoring taxonomy โ it owns the sync_status, points at the synced content-package
bytes in RustFS, and carries an optional PodDefinitionRef. LabletDefinition is retired as
a distinct type; a Lablet is just a single-part session whose one Form references a
cml_on_aws PodDefinition.
The rest of the taxonomy (Track, Exam, Blueprint, Module, Formset, FormItem) stays
catalogue metadata with a definition_status (draft โ published โ deprecated) but no
sync loop. Only the Form reconciles โ it is the leaf that carries deliverable bytes + the
optional pod.
classDiagram
class Form {
+str fqn
+str version
+str provisioning_source = content_package
+str definition_status
+str sync_status
+str content_ref
+PodDefinitionRef~None pod_ref
+str~None authorization_policy_id
+is_synced() bool
}
class PodDefinitionRef {
+str definition_id
+str version
+str pod_type
+str content_hash
}
class PartDefinition {
+str name
+str selector_pattern
+dict requirements
+int order
+bool gates_next
+list~PhaseDef part_lifecycle
}
class SessionPart {
+str session_id
+str part_definition_ref
+str form_ref
+list~str pod_ids
}
PartDefinition ..> Form : selects (pattern + requirements)
Form "1" o-- "0..1" PodDefinitionRef : optional pod
SessionPart --> Form : delivers (resolved by FQN)
SessionPart ..> PartDefinition : instantiated from
2.2 What does not change โ no new timed instanceยถ
The Form is a definition-plane resource: it lives on the catalogue/sync plane, not in
the timed runtime tree (Session โ SessionPart โ PodInstance โ Host). There is still no
separate timed Form instance โ the SessionPart remains the timed runtime that delivers
the form. The change is that the Form is no longer inert: it is a reconciled resource whose
desired/actual is its sync state, exactly the asymmetry ADR-051 already grants
content_package definitions.
| Plane | Resource | Reconciles toward | Tier |
|---|---|---|---|
| Catalogue / sync | Form (this ADR) |
sync_status (content present + fanned out) |
definition (content_package) |
| Timed runtime | SessionPart |
desired_status (delivery lifecycle) |
L2 TimedResource |
2.3 Pod binding moves to the Formยถ
PodDefinitionRef is owned by the Form, not the PartDefinition. A PartDefinition
selects a Form by selector pattern + requirements (unchanged โ ADR-045); the resolved
Form supplies the optional pod. Web/design forms (CCDE COR, CCIE DES) carry no
pod_ref; lab forms carry one. requirements.requires_pod becomes an admissibility check
against the resolved Form.pod_ref, not a separate part field.
2.4 The form-controller owns Form syncยถ
The Form sync loop (Mosaic โ RustFS โ LDS + SE fan-out) is owned by a dedicated
form-controller โ the generalization of the content-sync half of the current
lablet-controller (what ADR-054 earlier called
content-controller). It reconciles every Form regardless of use-case; seed catalogue/config
definitions are still seeded by CPA and do not reconcile.
flowchart LR
OP["Operator / author"] -->|"sync Form by FQN"| CPA["CPA"]
CPA -->|"desired sync"| FC["form-controller"]
FC -->|"download package"| MOS["Mosaic"]
FC -->|"upload bytes"| BLOB["RustFS / S3"]
FC -->|"fan-out"| LDS["LDS"]
FC -->|"fan-out"| SE["scenario-engine"]
FC -->|"sync_status"| CPA
style CPA fill:#6b7280,color:#fff
style FC fill:#475569,color:#fff
style SE fill:#0d9488,color:#fff
3. Consequencesยถ
Positive
- One synced unit (
Form) reused acrosslablet/practicelab/expertlab/expertdesign; no single-part-onlyLabletDefinitionspecial case. - Pod binding lives with the content that needs it; a
Formis a self-contained "content + optional pod" deliverable, reusable across parts and sessions. - The catalogue stays inert except at the leaf; the sync surface is exactly one type.
form-controllerhas a single, uniform contract (Form.sync_status).
Negative / trade-offs
- Re-homes
pod_reffromPartDefinitiontoFormand retiresLabletDefinitionas a type โ touches the catalogue model, session-model, and the seed format. Local-only, no migration window, so the cut is clean. - A
Formnow mixes authoring metadata (FQN, items) with a sync lifecycle; the L1/L2 instance model is unaffected becauseFormis definition-plane, not a timed instance.
Persistence โ Form is a state-based Neuroglia AggregateRoot (MotorRepository +
state_version) like every other definition (ADR-051); the legacy LabletDefinition sync
behaviour is preserved as @dispatch reducers, only generalised.
4. Relatedยถ
- definition-catalog-model.md โ Form sync + catalogue.
- session-model.md โ how a
SessionPartselects/delivers aForm. - resource-model.md โ definition vs instance planes.
- ADR-054 โ
form-controllerin the topology.