Complete the data model with a runtime environment and end-to-end test #481

Merged
kiara merged 8 commits from fricklerhandwerk/Fediversity:deployment-data-model-with-tests into main 2025-08-27 00:45:51 +02:00

Closes #103

At last, a fully fledged data model for what Fediversity really is and does. This comes with a test that exercises a very simple but functionally complete arrangement with all ingredients fo the business logic: a dummy resource (login shell), a dummy application (hello, which needs a shell to live in), a dummy environment (a single NixOS VM that allows for one, the operator's, login shell), and a deployment of that environment given a dummy configuration (that enables hello).

The next step will be to lift this purely evaluation-level test into a VM test which verifies that the resulting VM indeed has hello deployed to the operator's user account.

Caveats:

  • The exact naming has a bit of room for improvement, and may have diverged from the design document
  • The test is not as pedantically type safe as it could be, since we simply use types.raw for resources such as NixOS users settings which could be more finely delineated
Closes #103 At last, a fully fledged data model for what Fediversity really is and does. This comes with a test that exercises a very simple but functionally complete arrangement with all ingredients fo the business logic: a dummy resource (login shell), a dummy application (`hello`, which needs a shell to live in), a dummy environment (a single NixOS VM that allows for one, the operator's, login shell), and a deployment of that environment given a dummy configuration (that enables `hello`). The next step will be to lift this purely evaluation-level test into a VM test which verifies that the resulting VM indeed has `hello` deployed to the operator's user account. Caveats: - The exact naming has a bit of room for improvement, and may have diverged from the design document - The test is not as pedantically type safe as it could be, since we simply use `types.raw` for resources such as NixOS users settings which *could* be more finely delineated
add explanatory comment
All checks were successful
/ check-pre-commit (pull_request) Successful in 14s
/ check-data-model (pull_request) Successful in 30s
/ check-mastodon (pull_request) Successful in 22s
/ check-peertube (pull_request) Successful in 23s
/ check-panel (pull_request) Successful in 1m33s
/ check-deployment-basic (pull_request) Successful in 33s
/ check-deployment-cli (pull_request) Successful in 43s
/ check-deployment-panel (pull_request) Successful in 1m51s
1063be8c16
requested reviews from Niols, kiara 2025-07-29 17:24:07 +02:00
@ -15,3 +14,3 @@
options = {
input-type = mkOption {
type = deferredModule;
type = optionType;
Owner

while this change better aligns the semantics with the naming (input-type, output-type), from the DX perspective this seems to make for a slightly clunkier interface, having to manually invoke submodule on these arguments.

(to be fair, unlike the old version, the current one does also work with say raw, which indeed is a type rather than a module - tho as you noted, we might find more elegant alternatives to typing NixOS modules than raw.)

while this change better aligns the semantics with the naming (`input-type`, `output-type`), from the DX perspective this seems to make for a slightly clunkier interface, having to manually invoke `submodule` on these arguments. (to be fair, unlike the old version, the current one does also work with say `raw`, which indeed is a type rather than a module - tho as you noted, we might find more elegant alternatives to typing NixOS modules than `raw`.)
Author
Owner

i don't think it's clunkier. after letting it rest for a couple of days I actually was surprised why the field is not of type optionType, because why can a function not be str -> str?

i don't think it's clunkier. after letting it rest for a couple of days I actually was surprised why the field is not of type `optionType`, because why can a function not be `str -> str`?
fricklerhandwerk marked this conversation as resolved
@ -35,1 +98,3 @@
enable = lib.mkEnableOption "Hello in the shell";
options.enable = lib.mkEnableOption "Hello in the shell";
};
implementation = cfg: {
Owner

our demo code currently has modules depend on other parts of config as well - down the road we may need to consider if those might function as say resources instead, tho for the purpose of this PR this may do

our demo code currently has modules depend on other parts of `config` as well - down the road we may need to consider if those might function as say resources instead, tho for the purpose of this PR this may do
Author
Owner

the purpose of this PR is exactly to show a minimal example that one can still understand in limited time. in practice you will probably want a resource of the size and complexity of an entire NixOS VM, but here it would be in the way to asserting that the mechanism is sane.

the purpose of this PR is exactly to show a minimal example that one can still understand in limited time. in practice you will probably want a resource of the size and complexity of an entire NixOS VM, but here it would be in the way to asserting that the mechanism is sane.
kiara marked this conversation as resolved
@ -19,2 +21,4 @@
test-eval = {
/**
This tests a very simple arrangement that features all ingredients of the Fediversity business logic:
Owner

one might argue the business logic could include portability aspects as well, but sure

one might argue the business logic could include portability aspects as well, but sure
Author
Owner

oh yeah, maybe it should also say "as far as it is currently encoded".

oh yeah, maybe it should also say "as far as it is currently encoded".
kiara marked this conversation as resolved
@ -36,0 +108,4 @@
{ config, ... }:
{
resources.operator-environment.login-shell.username = "operator";
implementation = requests: {
Owner

this explicit interface to module functions (specifying output and input both) feels a bit clunky, is this not something that could be handled behind the scenes instead (see my branch)?

this explicit interface to module functions (specifying `output` and `input` both) feels a bit clunky, is this not something that could be handled behind the scenes instead (see my branch)?
Author
Owner

it sure is clunky, but that's what we have to ensure type safety. we don't strictly require it, we may as well reduce implementation to functionTo output-type, but it makes things more explicit. I'm open to loosening that strictness if we find a way to make sure that application/policy authors don't get confronted with even more inscrutable errors than we have already.

it sure is clunky, but that's what we have to ensure type safety. we don't strictly require it, we may as well reduce `implementation` to `functionTo output-type`, but it makes things more explicit. I'm open to loosening that strictness if we find a way to make sure that application/policy authors don't get confronted with even more inscrutable errors than we have already.
Owner

so, i might wanna check if handling this wiring in data-model.nix (1) enforces type-safety and (2) doesn't complicate error messages too much - is that correct?

so, i might wanna check if handling this wiring in `data-model.nix` (1) enforces type-safety and (2) doesn't complicate error messages too much - is that correct?
Author
Owner

hmm i'd say it's more about practicality. will application authors be able to work fluently with an interface that doesn't force type checks on them?

hmm i'd say it's more about practicality. will application authors be able to work fluently with an interface that doesn't force type checks on them?
Owner

i mean, hence i proposed i'd verify that'd still work 😅

i mean, hence i proposed i'd verify that'd still work 😅
Owner

in fact, even this type-checks while it shouldn't, implying it doesn't work as intended? like i'd be inclined to maybe leave it out until we get it to do what we wanted out of it.

in fact, even [this](https://git.fediversity.eu/fricklerhandwerk/Fediversity/compare/deployment-data-model-with-tests...kiara:repro-broken-module-function-input-check) type-checks while it shouldn't, implying it doesn't work as intended? like i'd be inclined to maybe leave it out until we get it to do what we wanted out of it.
Owner
i.e. [simplification](https://git.fediversity.eu/fricklerhandwerk/Fediversity/compare/deployment-data-model-with-tests...kiara:unwire-module-functions)
Author
Owner

Yes I see. While I can't prove it at the moment, I think skipping the types loses us a valuable opportunity to generate rich documentation for application authors and hosting providers. And that would matter, because both need a very clear understanding of the objects of interest here, and how to put things together.

Yes I see. While I can't prove it at the moment, I think skipping the types loses us a valuable opportunity to generate rich documentation for application authors and hosting providers. And that would matter, because both need a very clear understanding of the objects of interest here, and how to put things together.
Owner

thanks. i'm sympathetic to facilitating generation of type-based documentation.
while we don't have an indication the clunky developer interface is necessary to achieve either type-checking or doc gen tho, i may try my hand at a version balancing those two. that said, i wouldn't see that as blocking for the purpose of the current PR.

thanks. i'm sympathetic to facilitating generation of type-based documentation. while we don't have an indication the clunky developer interface is necessary to achieve either type-checking or doc gen tho, i may try my hand at a version balancing those two. that said, i wouldn't see that as blocking for the purpose of the current PR.
Owner
opened at https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/9
Owner

i see that at module-interfaces input types do successfully verify input values, so we may need further investigation on why it didn't here.

i see that at `module-interfaces` [input types](https://github.com/fricklerhandwerk/module-interfaces/blob/caeb171fde3a6ce2dc61f4e59834df9e7bf584a0/interface.nix#L47-L58) do successfully verify [input values](https://github.com/fricklerhandwerk/module-interfaces/blob/caeb171fde3a6ce2dc61f4e59834df9e7bf584a0/example/string-provider.nix#L16-L21), so we may need further investigation on why it didn't here.
Owner

my hunch now is our inputs remained unchecked since we never referenced them after assignment, whereas module-interfaces did for both consumer and provider, explicitly making their outputs depend on their (module-provided) inputs.

it achieves this by having the interface's provider for the function's abstract type, while using the consumer as the instantiation where concrete values for the function (here: string-provider) and its input (here: from string-consumer) may be plugged in to compute the output (here: checked in the test).

on the other hand, our function implementations (parallel to the above providers) do not yet get module-provided inputs (as above consumer's) for their arguments:

  • for applications' implementation, resources is hampered from switching to such a modular imperative invocation due to its function-based invocation in a mapAttrs in environments' deployment.
  • for environments' implementation, so far invocation remains functional, thru function deployment, in turn called from the tests.

a solution would seem to move their relevant inputs to a deployment entity, such that invocations could be switched from functional to modular.

my hunch now is our `input`s remained unchecked since we never referenced them after assignment, whereas `module-interfaces` did for both [consumer](https://github.com/fricklerhandwerk/module-interfaces/blob/caeb171fde3a6ce2dc61f4e59834df9e7bf584a0/interface.nix#L39) and [provider](https://github.com/fricklerhandwerk/module-interfaces/blob/caeb171fde3a6ce2dc61f4e59834df9e7bf584a0/example/string-provider.nix#L20), explicitly making their outputs depend on their (module-provided) inputs. ~~it achieves this by having the interface's `provider` for the function's abstract type, while using the `consumer` as the instantiation where concrete values for the function (here: `string-provider`) and its input (here: from `string-consumer`) may be plugged in to compute the output (here: checked in the test).~~ ~~on the other hand, our function `implementation`s (parallel to the above `provider`s) do not yet get module-provided `input`s (as above `consumer`'s) for their arguments:~~ - ~~for `applications`' `implementation`, `resources` is hampered from switching to such a modular imperative invocation due to its function-based invocation in a `mapAttrs` in `environments`' `deployment`.~~ - ~~for `environments`' `implementation`, so far invocation remains functional, thru function `deployment`, in turn called from the tests.~~ ~~a solution would seem to move their relevant inputs to a deployment entity, such that invocations could be switched from functional to modular.~~
Owner
fix at https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/13 on the input type checks noted at https://git.fediversity.eu/Fediversity/Fediversity/pulls/481/files#issuecomment-9278
@ -27,0 +70,4 @@
};
config.resource-type = types.raw; # TODO: splice out the user type from NixOS
config.apply =
requests:
Owner

so these functions now are exposed to requests for other resources as well. did we find a use-case for that? otherwise this may mostly unnecessarily complicate the interface here.

so these functions now are exposed to requests for other resources as well. did we find a use-case for that? otherwise this may mostly unnecessarily complicate the interface here.
Author
Owner

yes, this is why I left the TODO to consider transposing the resource attrsOf, so we could pass just the request(s) for that same resource to this function. we supposedly wouldn't (want to) care or even know about other resources. so arguably this exact formulation is more or less accidental.

yes, this is why I left the TODO to consider transposing the resource `attrsOf`, so we could pass just the request(s) for that same resource to this function. we supposedly wouldn't (want to) care or even know about other resources. so arguably this exact formulation is more or less accidental.
@ -54,3 +150,4 @@
};
}
);
resources = fediversity.applications.hello.resources fediversity.example-configuration.applications.hello;
Owner

what's up with the mentioned resources being a function - could it take multiple inputs instead, and how might that affect the interface here?

what's up with the mentioned `resources` being a function - could it take multiple inputs instead, and how might that affect the interface here?
Author
Owner

why would it take multiple inputs? here in the test we only touch it in order to do some granular analysis of intermediate steps. in practice this is not part of the public interface. each application's resource-mapping takes one configuration value for that application.

why would it take multiple inputs? here in the test we only touch it in order to do some granular analysis of intermediate steps. in practice this is not part of the public interface. each application's resource-mapping takes one configuration value for that application.
kiara marked this conversation as resolved
@ -56,1 +152,4 @@
);
resources = fediversity.applications.hello.resources fediversity.example-configuration.applications.hello;
hello-shell = (resources).resources.hello.login-shell;
environment = fediversity.environments.single-nixos-vm.resources.operator-environment.login-shell;
Owner

is a shell an environment?

is a shell an environment?
Author
Owner

in this example, the "single NixOS VM" environment has a shell as a resource.

divergent thought: if we could deploy single users to NixOS VMs (why not, in principle), a shell could play the role of the environment, but I'm out of creativity for what a resource inside that shell may be.

in this example, the "single NixOS VM" environment has a shell as a *resource*. divergent thought: if we could deploy single users to NixOS VMs (why not, in principle), a shell could play the role of the environment, but I'm out of creativity for what a resource inside that shell may be.
kiara marked this conversation as resolved
@ -59,3 +163,1 @@
inherit (fediversity)
example-configuration
;
rec {
Owner

i recall in the past sidestepping the simplest test of straight-up verifying the full contents of a deployment had been a cope for unresolved evaluation errors. i imagine mkDeployment / evalModules may complicate full inspection, but out of curiosity, is the set-up here still to any extent a cope?

i recall in the past sidestepping the simplest test of straight-up verifying the full contents of a deployment had been a cope for unresolved evaluation errors. i imagine `mkDeployment` / `evalModules` may complicate full inspection, but out of curiosity, is the set-up here still to any extent a cope?
Author
Owner

no, mkDeployment is simply required to at least get something out of the deployment's module expression. looking inside is not very helpful though, as you see from the test. it has a bunch of functions in there that are called by nixops4 at runtime, but we can't do much about them here.

no, `mkDeployment` is simply required to at least get something out of the deployment's module expression. looking inside is not very helpful though, as you see from the test. it has a bunch of functions in there that are called by nixops4 at runtime, but we can't do much about them here.
kiara marked this conversation as resolved
@ -31,0 +57,4 @@
};
request = mkOption {
description = "Options for declaring resource requirements by an application, a description of how the resource is consumed or accessed";
type = deferredModuleWith { staticModules = [ { _class = "fediversity-resource-request"; } ]; };
Owner

why not use deferredModuleWith's class parameter?

why not use `deferredModuleWith`'s `class` parameter?
Author
Owner

oh yes, good point

oh yes, good point
Owner
tackled in https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/4
Owner

(my earlier comment was off btw - it isn't deferredModuleWith that has a class parameter, just subModuleWith does)

(my earlier comment was off btw - it isn't `deferredModuleWith` that has a `class` parameter, just `subModuleWith` does)
fricklerhandwerk marked this conversation as resolved
@ -27,0 +69,4 @@
};
};
config.resource-type = types.raw; # TODO: splice out the user type from NixOS
config.apply =
Owner

config = { ... } to contain these two?

`config = { ... }` to contain these two?
Author
Owner

yes I left it to keep some intermediate diff small, will fix up...

yes I left it to keep some intermediate diff small, will fix up...
Owner
see https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/3
kiara marked this conversation as resolved
@ -27,0 +74,4 @@
let
# Filter out requests that need wheel if policy doesn't allow it
validRequests = lib.filterAttrs (
_name: req: !req.login-shell.wheel || config.wheel
Owner

when i tested on my branch, i thought that config here seemed to refer to the configuration set by this policy in the abstract (i.e. with apply and resource-type), rather than the actual instantiated configuration of the policy (i.e. with wheel and username). how did you handle that here?

when i tested on my branch, i thought that `config` here seemed to refer to the configuration _set_ by this policy in the abstract (i.e. with `apply` and `resource-type`), rather than the actual instantiated configuration of the policy (i.e. with `wheel` and `username`). how did you handle that here?
Author
Owner

I'm not sure I get the question. Yes, the mapping deals with the specific instance of a policy, as one can (hopefully) observe in the test, where we set the username to "operator".

I'm not sure I get the question. Yes, the mapping deals with the specific instance of a policy, as one can (hopefully) observe in the test, where we set the username to `"operator"`.
kiara marked this conversation as resolved
@ -68,0 +125,4 @@
environments = mkOption {
description = "Run-time environments for Fediversity applications to be deployed to";
type = attrsOf (
submodule (environment: {
Owner

maybe submoduleWith with class arg?

maybe `submoduleWith` with `class` arg?
Author
Owner

I don't think it makes a difference here, except more braces?

I don't think it makes a difference here, except more braces?
Owner

maybe? technically its implementation mentions the class a bit more, but you may understand better to what extent that impacts our use-cases

maybe? technically its [implementation](https://github.com/hsjobeki/nixpkgs/blob/f0efec9cacfa9aef98a3c70fda2753b9825e262f/lib/types.nix#L1170-L1330) mentions the class a bit more, but you may understand better to what extent that impacts our use-cases
Owner
see https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/7
@ -68,0 +134,4 @@
Setting this is optional, but provides a place to declare that information for programmatic use in the resource mapping.
'';
# TODO: maybe transpose, and group the resources by type instead
Owner

had we revisited this consideration already? what do we see as arguments for either option?

had we revisited this consideration already? what do we see as arguments for either option?
Author
Owner

transposing may be slightly easier on the eyes in the two places where we iterate over resources. not sure, it's just a thought we can ignore until it starts hurting. at the moment we don't have to guarantee interface stability and need to experiment with it in practice to get an idea what works well

transposing may be slightly easier on the eyes in the two places where we iterate over resources. not sure, it's just a thought we can ignore until it starts hurting. at the moment we don't have to guarantee interface stability and need to experiment with it in practice to get an idea what works well
kiara marked this conversation as resolved
@ -68,0 +156,4 @@
};
# TODO(@fricklerhandwerk): maybe this should be a separate thing such as `fediversity-setup`,
# which makes explicit which applications and environments are available.
# then the deployments can simply be the result of the function application baked into this module.
Owner

👍

:+1:
kiara marked this conversation as resolved
kiara approved these changes 2025-07-29 20:13:21 +02:00
Dismissed
kiara left a comment
Owner

i'll probably still try and compare with my branch later. either way tho, this looks like a clear improvement over main.

i'll probably still try and compare with my branch later. either way tho, this looks like a clear improvement over `main`.
@ -68,0 +151,4 @@
readOnly = true;
default = {
input-type = application-resources;
output-type = nixops4Deployment;
Owner

we may want to un-hardcode this deployment method in a future iteration, in line with earlier terminology involving multiple distinct run-time environments with their own providers. this could maybe be handled with an attrTag type.

which maybe also begs the question, could deployment providers be handled like resources, similarly to how a host's fediversity setup might provide configured resources for the deployments?

we may want to un-hardcode this deployment method in a future iteration, in line with earlier terminology involving multiple distinct run-time environments with their own providers. this could maybe be handled with an `attrTag` type. which maybe also begs the question, could deployment providers be handled like resources, similarly to how a host's fediversity setup might provide configured resources for the deployments?
Author
Owner

the deployment method is decidedly not hard-coded here at all. the environment owner specifies that by providing an implementation, where they can use any provider just the way they want. environments are already attrsOf, so you can have as many ways as you wish to realise some configuration.

also, the decoupling of providers and deployments already happens at the nixops4 layer (via the providers field). other than that, how you arrange the code on your end as an environment owner is your responsibility.

the deployment method is decidedly not hard-coded here at all. the environment owner specifies that by providing an `implementation`, where they can use any provider just the way they want. environments are already `attrsOf`, so you can have as many ways as you wish to realise some configuration. also, the decoupling of providers and deployments already happens at the nixops4 layer (via the `providers` field). other than that, how you arrange the code on your end as an environment owner is your responsibility.
Owner

the deployment method is decidedly not hard-coded here at all.

what of

?

> the deployment method is decidedly not hard-coded here at all. what of https://git.fediversity.eu/Fediversity/Fediversity/src/commit/1063be8c16b99d5d302f78f4773e0f7cd9643eb8/deployment/data-model.nix#L43?
Author
Owner

ah that. but that's just a data structure you can convert to for use by any deployment method.

ah that. but that's just a data structure you can convert to for use by any deployment method.
Owner

it seems just out of place here tho. surely, it wasn't our top-level that would correspond to their structure in the first place? like, should this line not just be part of our nixops4Deployment definition up a few lines?

it seems just out of place here tho. surely, it wasn't our top-level that would correspond to their structure in the first place? like, should this line not just be part of our `nixops4Deployment` definition up a few lines?
Owner
https://git.fediversity.eu/fricklerhandwerk/Fediversity/pulls/6
put config stuff in an attrset
All checks were successful
/ check-pre-commit (pull_request) Successful in 14s
/ check-data-model (pull_request) Successful in 30s
/ check-mastodon (pull_request) Successful in 22s
/ check-peertube (pull_request) Successful in 21s
/ check-panel (pull_request) Successful in 1m30s
/ check-deployment-basic (pull_request) Successful in 32s
/ check-deployment-cli (pull_request) Successful in 43s
/ check-deployment-panel (pull_request) Successful in 1m49s
7ce3902851
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

kiara approved these changes 2025-07-31 17:36:59 +02:00
Dismissed
Merge pull request 'move nixops4Deployment class' (#6) from kiara/Fediversity:data-model-fix-root-class into deployment-data-model-with-tests
All checks were successful
/ check-pre-commit (pull_request) Successful in 14s
/ check-data-model (pull_request) Successful in 30s
/ check-mastodon (pull_request) Successful in 21s
/ check-peertube (pull_request) Successful in 21s
/ check-panel (pull_request) Successful in 1m32s
/ check-deployment-basic (pull_request) Successful in 33s
/ check-deployment-cli (pull_request) Successful in 43s
/ check-deployment-panel (pull_request) Successful in 1m48s
9c219341b1
Reviewed-on: fricklerhandwerk/Fediversity#6
Reviewed-by: Valentin Gagarin <valentin.gagarin@tweag.io>
kiara dismissed kiara's review 2025-07-31 18:18:01 +02:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

Owner

The next step will be to lift this purely evaluation-level test into a VM test which verifies that the resulting VM indeed has hello deployed to the operator's user account.

maybe i can try this now, having gone over the other stuff

edit: wip branch here here

> The next step will be to lift this purely evaluation-level test into a VM test which verifies that the resulting VM indeed has `hello` deployed to the operator's user account. maybe i can try this now, having gone over the other stuff edit: wip branch ~~[here](https://git.fediversity.eu/fricklerhandwerk/Fediversity/compare/deployment-data-model-with-tests...kiara:repro-data-model-deployment-recursion)~~ [here](https://git.fediversity.eu/fricklerhandwerk/Fediversity/compare/deployment-data-model-with-tests...kiara/Fediversity:data-model-test-deployment-plain)
kiara approved these changes 2025-08-01 17:04:58 +02:00
Merge branch 'main' into deployment-data-model-with-tests
All checks were successful
/ check-pre-commit (pull_request) Successful in 15s
/ check-data-model (pull_request) Successful in 34s
/ check-mastodon (pull_request) Successful in 23s
/ check-peertube (pull_request) Successful in 23s
/ check-panel (pull_request) Successful in 1m33s
/ check-deployment-basic (pull_request) Successful in 37s
/ check-deployment-cli (pull_request) Successful in 46s
/ check-deployment-panel (pull_request) Successful in 1m56s
/ check-resources (pull_request) Successful in 4m31s
8a28fa521f
kiara merged commit 9d903f3ef7 into main 2025-08-27 00:45:51 +02:00
kiara deleted branch deployment-data-model-with-tests 2025-08-27 00:45:52 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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: fediversity/fediversity#481
No description provided.