design: service actions reference undeclared services — no base state for reconciliation #34

Closed
opened 2026-06-12 18:11:44 +02:00 by yorunikakeru · 0 comments
Owner

Problem

enable/disable in profiles validate the service name only by shape (Service.mkServiceName, src/DSL/Service.hs) — there is no check that a service section with that name exists. A config with disable "docker" and zero service sections compiles silently. The action target (Service.ServiceName) and the section name (ServiceSectionName) are unrelated types, so nothing links them even structurally.

This is more than a missing referential check. Conditional profiles have while-semantics: when the condition goes false (steam exits), the planner must reconcile the service back to its base state — and today the configuration does not define one. disable "docker" in a conditional profile leaves the runtime with no answer to "what should docker be now?". The system drifts from the configuration and stays there.

Proposal

Make the service section a declaration: "this service is managed by the configuration, here is its base state".

  1. Referential check in Validate.hs: every service targeted by enable/disable (in any profile) must have a corresponding service section. Undeclared target → ValidationError. NixOS-style: not declared — not managed.
  2. Explicit base state in the service section, e.g. a baseState Enabled field — the state the reconciler returns the service to when no conditional profile is active. Explicit field rather than reusing enable/disable inside the section: "desired under condition" and "base" are different roles and should not share constructors.

Notes

  • This encodes the DSL side of the reconciliation contract: actions are desired-state assertions, conditions are while-predicates, revert = recompute from base + active profiles. The planner-side reconciliation loop is a separate (runtime repo) concern; this issue only gives it a well-defined base layer to return to.
  • Interacts with the priority model (plain enable/disable = priority 100, interConflict in Validate.hs): base state is what applies when no profile acts on the service, so it needs no priority.
  • Cost: users must declare every service any profile touches. Consistent with the existing fail-fast validation philosophy.
## Problem `enable`/`disable` in profiles validate the service name only by shape (`Service.mkServiceName`, src/DSL/Service.hs) — there is no check that a `service` section with that name exists. A config with `disable "docker"` and zero `service` sections compiles silently. The action target (`Service.ServiceName`) and the section name (`ServiceSectionName`) are unrelated types, so nothing links them even structurally. This is more than a missing referential check. Conditional profiles have while-semantics: when the condition goes false (steam exits), the planner must reconcile the service back to its **base state** — and today the configuration does not define one. `disable "docker"` in a conditional profile leaves the runtime with no answer to "what should docker be now?". The system drifts from the configuration and stays there. ## Proposal Make the `service` section a **declaration**: "this service is managed by the configuration, here is its base state". 1. **Referential check in `Validate.hs`**: every service targeted by `enable`/`disable` (in any profile) must have a corresponding `service` section. Undeclared target → `ValidationError`. NixOS-style: not declared — not managed. 2. **Explicit base state in the `service` section**, e.g. a `baseState Enabled` field — the state the reconciler returns the service to when no conditional profile is active. Explicit field rather than reusing `enable`/`disable` inside the section: "desired under condition" and "base" are different roles and should not share constructors. ## Notes - This encodes the DSL side of the reconciliation contract: actions are desired-state assertions, conditions are while-predicates, revert = recompute from base + active profiles. The planner-side reconciliation loop is a separate (runtime repo) concern; this issue only gives it a well-defined base layer to return to. - Interacts with the priority model (plain `enable`/`disable` = priority 100, `interConflict` in Validate.hs): base state is what applies when **no** profile acts on the service, so it needs no priority. - Cost: users must declare every service any profile touches. Consistent with the existing fail-fast validation philosophy.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
FrogOS/DSL#34
No description provided.