Model service machines: no runtime, x86 only, no guest sizing #14
No reviewers
Labels
No labels
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
allod/inventory!14
Loading…
Reference in a new issue
No description provided.
Delete branch "agent/service-machine-schema"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Inventory can now describe a rented, internet-facing machine that this fleet did not create, under a new
servicetype. Nothing about any existing machine changes:allod-dev,privacy-1andnexusevaluate to exactly what they did before, andscripts/vm-specs.jsonis byte-identical, because no service machine is added — the first one is added when its host is rented. What proves it isnix flake check: four checks covering the three refusals this type exists to make, plus the shape rules everything else classifies on. If it is wrong, revert the commits; nothing downstream reads aservicemachine yet.An adversarial review pass by a second model found five issues after the first commit; the fixes are in
b527536and the findings are recorded in a comment below. One mattered beyond this PR's own scope: platform validation was not in the chain thatmachinesconsumers force, so a machine with noplatformreachedarchetypesunvalidated.What changed
A
servicemachine is a host some provider runs. Three schema rules follow from that, and each is an evaluation error rather than a field that is quietly ignored:runtime. The existing "hypervisors carry no runtime" rule widens to aruntimeFreeTypes = [ "hypervisor" "service" ]set. A hypervisor is in it because it runs the guests; a service machine is in it because some provider's hypervisor runs it, nothing here can see or configure that, and there is no guest module to select. The rejected alternative —runtime = "rented"— is recorded in the code comment: it keeps every machine uniform at the cost of putting a fictional virtualisation system in a field that otherwise names real ones.platformmust bex86_64-linux.platformsays nothing about who hosts the machine, solib.systems.flakeExposedwould accept ARM today and the first ARM machine would fail somewhere confusing during provisioning. The refusal is early and names the missing cross-architecture build path.memory_mb,vcpus,disk_gb,mac. For a guest these are instructions a hypervisor acts on; on a rented machine they would describe what is being paid for, and nothing in the file distinguishes the two.Service machines are excluded from
lib.vmSpecsJsonfor the same reason hypervisors are: that file tells host tooling how to size and start guests.runtimeDiagnosticsbecomesmachineDiagnosticsand grows from four diagnostic sets to eight. Four are the runtime rules, two are the service rules, and two —missingType/nonStringType— are shape rules the review showed were missing. The disjointness property the original design argues for is preserved and now stated for the wider set: a machine with exactly one problem still trips exactly one diagnostic, which is what lets each fixture be pinned to the diagnostic it claims to exercise rather than to "evaluation failed for some reason".Validation moved into the chain consumers force
checkedMachinesforcesmkVmSpecs, so anything asserted there reaches a consumer reading rawmachines— the surfacearchetypesactually uses. Platform validation was not there: it hung offlib.supportedPlatforms, which that path never forces, so a machine with noplatformwas handed out unvalidated and only failed later somewhere less obvious.missingPlatformandinvalidPlatformare now diagnostics asserted insidemkVmSpecs, alongside the newtypeshape rules, andsupportedPlatformsderives from the same chain.Inventory deliberately does not validate the
typevalue against a set of known archetypes. That set belongs toarchetypes, which owns the builders and already rejects a machine whose type has no builder.Checks
runtime-fact-mutationskeeps its name deliberately.allod/archetypesreadsinventory.checks.<system>.runtime-fact-mutationsby name and throws when it is absent, because enum validation for the runtime fact is this repo's contract to that one; renaming would break that link for no gain. A comment in the file records this. It gains fixtures for a service machine declaring a runtime, for missing and non-stringtype, for missing and invalidplatform, and for a service machine with noplatformat all — that last one tripsmissingPlatformrather thanserviceForeignPlatform, which is the guard the review showed was a hole.service-machine-mutationsis new: a positive fixture proving the rules admit the machine they describe and that it stays out ofvmSpecsJson, then one sabotage per rule, each pinned to its own diagnostic and each proven to fail the realmkVmSpecsJsonpath.diagnosticFieldsis derived frombuiltins.attrNames (machineDiagnostics { })rather than hand-written. The review reproduced the alternative: add a diagnostic and forget the list, andpinnedTosilently stops looking at it.New exports
lib.runtimeFreeTypesandlib.isGuestMachine. The review foundarchetypeskeeping three separate copies of this classification, one of which had already gone stale against theservicetype and would have aborted on the first rented host. Machine facts belong here; the export is what stops a fourth copy appearing.Risk
R1. The change is additive to a schema whose only consumers read guest machines, and no service machine exists for anything to read. The widened runtime-free filter is a no-op while
serviceis unused.The one behavioural change reaching existing data is that
typeandplatformshape errors now fail on themachinespath rather than only viasupportedPlatforms. Valid data is unaffected; invalid data fails earlier and more legibly than before.Validation
nix flake checkpasses:vm-specs-json,repository-registry,runtime-fact-mutations,service-machine-mutations.Every witness was confirmed non-vacuous by sabotaging production and observing red:
serviceForeignPlatformassertion withassert trueturnedforeign platform: fails mkVmSpecsJsonred while itspinnedTohalf stayed green — exactly the split those two halves exist to distinguish.macfromserviceGuestFieldsturned three lines red.platformfromprivacy-1and forcingmachinesnow fails withinventory machines missing platform: privacy-1; before the fix it returned successfully.Two defects in the checks themselves were found and fixed this way rather than by inspection: the per-field sizing fixtures originally derived from the very list under test, so deleting a field deleted its own witness; and the field-list comparison pinned list order, so a harmless reorder turned it red. It now compares sorted sets against an independently written list.
scripts/vm-specs.jsonneeds no regeneration; thevm-specs-jsondrift check confirms it still matches.Closes allod/inventory#13
Adversarial review pass (gpt-5.6-sol, xhigh) — findings and fixes
An independent read-only review was run against
d86175ewith a different model, driven to sabotage production code and observe whether the checks went red. It found five issues; four are fixed inb527536and one is answered.1. High, fixed — a machine with no
platformreachedmachinesconsumers unvalidated. Platform validation hung offsupportedPlatforms, whichcheckedMachinesnever forces, so the surfacearchetypesactually reads could hand out a machine with noplatformat all. This was pre-existing, but this PR's own comment claimed the opposite, so it is fixed here rather than deferred:missingPlatformandinvalidPlatformare now diagnostics inmachineDiagnostics, asserted insidemkVmSpecs. Forcingmachinesnow fails withinventory machines missing platform: <name>where it previously succeeded.Verified by removing
platformfromprivacy-1and forcingf.machines: green before the fix, red after.2. High, fixed —
typewas read before it was validated.isRuntimeFreedoesbuiltins.elem m.type, so a machine with notypeaborted with a rawattribute 'type' missing— an errortryEvalcannot catch, so no fixture could ever pin it, andmachineDiagnosticsdid not have the "pure, non-throwing" property it claimed.missingTypeandnonStringTypeare now guarded diagnostics classified before anything reads the field.Inventory deliberately does not validate the
typevalue against a set of known archetypes. That set belongs toarchetypes, which owns the builders and already rejects a machine whose type has no builder. Duplicating it here would be a second source of truth for a fact this repo does not own.3. Medium, fixed —
diagnosticFieldswas a second, unvalidated registry. A diagnostic added tomachineDiagnosticsbut forgotten in the hand-written list would be silently skipped bypinnedTo, so a fixture tripping two diagnostics still reported as pinned to one. The reviewer reproduced this by adding an overlapping diagnostic and watching the check stay green. It is now derived:builtins.attrNames (machineDiagnostics { }).4. Low, fixed — the guest-field witness pinned list order. Reordering
serviceGuestFieldschanged no behaviour but turned the check red. Now compared as sorted sets.5. Corrected a false comment. The PR claimed the service-shape rules assert before the runtime rules. They do not:
runtimeDeclaredasserts before it forcesserviceShape, so a machine wrong in two ways reports the runtime message. Nothing depends on the order — every fixture declares exactly one problem, which is what the disjointness property buys — but the comment now says what actually happens.Six new pinned fixtures cover the added diagnostics, including a service machine with no
platformat all, which tripsmissingPlatformrather thanserviceForeignPlatform. That case is exactly the hole finding 1 opened, so it is now a witness rather than an argument.Also added
lib.runtimeFreeTypesandlib.isGuestMachineare now exported. The review found thatarchetypeskeeps three separate copies of this classification and that one of them —runtime-module-selection's hypervisor-only filter — had already gone stale against theservicetype and would have aborted on the first rented host. That is fixed on the archetypes side; the export is what stops the next copy appearing.Not changed
The reviewer suggested collapsing the four guest-sizing sabotages to one, citing the one-sabotage-per-validator policy. Kept: with the field list now compared as a set, the four fixtures are what stop a validator that only ever inspects the first field from passing. They are pure evaluation and cost nothing.
Second-round adversarial review — the fixes themselves
The first round reviewed
d86175e; the fixes landed inb527536. Those fixes were written by the same author who wrote the defects and verified only by sabotages that author designed, so a second round was run with the fix commit as its target. It confirmed F1–F4 closed and found two new defects, both introduced by the fixes. Both are fixed in50f0ec1and0c41feb.1. High, fixed — the newly exported
lib.isGuestMachinehad no witness at all. The reviewer replaced it withisGuestMachine = _: true;and both mutation checks stayed green, because nothing in this repo consumes it —archetypesdoes. An exported interface that cannot be shown to fail is exactly what principle 11 refuses, and its consumers living in another repo is the reason it needs a witness here, not the reason it does not.It was also partial:
isGuestMachine { }raised a rawattribute 'type' missingthatbuiltins.tryEvalcannot catch, andisGuestMachine { type = 42; }returnedtrue, silently classifying a malformed machine as a guest. That is the same defect this PR had just fixed internally by makingmachineDiagnosticsnon-throwing — reintroduced one commit later in the export.Now:
isGuestMachineis hoisted into the flake'sletso the check drives the exact value consumers get rather than a second copy; it raises named, catchable errors for missing and non-stringtype; andservice-machine-mutationscarries seven witnesses over it. The reviewer's exact sabotage now fails with four errors.2. Low, fixed — "shape before meaning" was false, and it had changed a diagnostic. A let-binding's assertions fire when the binding is forced, not where it is written.
machineShapewas threaded through asserviceShape's return value, which made it the last thing evaluated, so a machine missing bothplatformandruntimereported the runtime problem — where the old code reported the platform one. The comment claimed the opposite.mkVmSpecsnow ends in an explicitbuiltins.seq machineShape (builtins.seq serviceShape (builtins.seq runtimeShape ...)), so the order is produced by forcing rather than by layout. Verified: that same input now reportsinventory machines missing platform: privacy-1, restoring the old diagnostic and making the claim true. The four runtime assertions are consolidated into one binding in written order, so written order and actual order now agree throughout.The
lib.runtimeFreeTypesandlib.isGuestMachinerows were also missing from the exported-outputs table; added.What the second round confirmed
platformfrom a real machine now fails throughmachines,lib.supportedPlatformsandcheckswith the intended diagnostic. No recursion from the reworkedsupportedPlatforms.typereach their named diagnostics throughcheckedMachinesandmkVmSpecsJson.machineDiagnostics, sobuiltins.attrNames (machineDiagnostics { })yields the full set, and adding an overlapping diagnostic makespinnedTofail.serviceGuestFieldsstays green; deletingmacgoes red.typedfilter does not create a bypass:type = 42with guest sizing set is rejected asnonStringTypeon bothmachinesandvmSpecsJson.One behavioural change is worth stating plainly rather than burying:
lib.supportedPlatformsis now stricter. A machine with a valid platform but a missingruntimeused to return a platform list from the old platform-only calculation; it now fails. That input was already invalid undercheckedMachines, so this is fail-closed strengthening rather than a regression, but it is a real difference.