Product input manifest, kernel requirement, and what the host said¶
Status: recorded observations from milestone M18, reference profile, 2026-09-13; the schema rules amended on 2026-09-29 (D103 to D105)
This page records the two Aegis-owned schemas, the payloads this repository ships under them, and what the reference machine answered when each asserted feature was probed. Everything below is an observation with a command. Nothing here builds an image, builds a kernel, boots anything, or contacts a producer repository, and a pass is development evidence on one workstation that closes no image, kernel, boot, hardware or release gate.
What is shipped¶
| File | Role | Schema | Requirements |
|---|---|---|---|
build/product-input.json |
the product input manifest Aegis hands to an image builder | aegis.p01.product-input.v1 |
REQ-P01-01, REQ-P01-06, REQ-P01-08, REQ-GOV-02 |
build/kernel-requirement.json |
the kernel a conforming product must be built with | aegis.p01-nucleus.kernel-requirement.v1 |
REQ-P01-09, REQ-P06-05, REQ-P07-01, REQ-P07-06, REQ-P13-02, REQ-P03-06, REQ-BOOT-01 |
build/kernel-requirement.reference.json |
what the reference profile must keep providing for the local milestones to stay runnable | the same schema | the same set, minus the realtime row |
build/mkosi.conf |
the P01 image definition mkosi summary parses |
mkosi 27 configuration | REQ-P01-01, REQ-P01-06, REQ-P02-06 |
The reviewed drop-ins in build/repart.d and build/sysupdate.d cite their
private sources in their own header comments. JSON has no comment syntax, so the
three payload files cite theirs differently and in-band: every feature row
carries a required-by identifier that resolves in
docs/roadmap/requirements.md, and that page carries the export id and digest
of the source each requirement came from. No private path and no source
fragment appears in any of them.
| File | What it is |
|---|---|
build/kernel-requirement.schema.json |
the JSON Schema of aegis.p01-nucleus.kernel-requirement.v1, generated from the Rust types (D105) |
build/product-input.schema.json |
the JSON Schema of aegis.p01.product-input.v1, generated the same way |
The two schemas¶
crates/aegis-fabrica-defs owns both. The field primitives are in src/field.rs
and every one of them validates while the payload is being decoded, through
#[serde(try_from = "String")], so an ill-formed field is a refusal rather than
a value that is checked later or not at all.
A payload is a JSON object, and so is every struct nested in one. serde's
derived decoder also reads a struct from a JSON array of its values in
declaration order, where deny_unknown_fields has no key to refuse, and a probe
on 2026-09-29 found KernelRequirement::decode returning Ok for the reviewed
requirement written that way. Both contracts now refuse the array form at every
level, schema peek included, as Malformed with expected a JSON object:
each struct's Deserialize asks for a map and hands only its entries to the
decoder serde derives on a private field mirror (src/payload.rs).
tests/array_form.rs sends the array form of both payloads and of every nested
struct.
The manifest states what docs/integration/stack.md asks of every crossing of
the Aegis boundary: a schema, a correlation identifier, an exact revision,
bounded retries and a recorded result. It adds the pinned distribution snapshot
(D18), the four configuration references and the boot kernel identity (D07).
Validation is against the real files. validate_definitions takes the text of
the reviewed repart.d drop-ins and the sysupdate.d transfer and runs them
through the same parsers the M03 gate uses; the crate opens nothing itself.
A definition set that does not parse is a DefinitionRefused, and one that
parses but does not meet a recorded requirement -- dropping the alternate root
slot, for instance -- is a RequirementNotMet naming the finding.
The kernel requirement is a payload rather than a fixed symbol list. That distinction is the milestone's, and it is checkable: the crate names no Kconfig symbol in code at all. Every symbol lives in a JSON row of the form
{
"symbol": "CONFIG_BPF_LSM",
"state": "built-in",
"probe": "lsm-list",
"required-by": "REQ-P06-05"
}
so a consumer adds a feature by adding a row, not by changing this repository.
M09 pins the consumers that read it: cordanaLLM/imago's pkg/kernel decodes
this payload at the pinned commit (D92), and since cordanaLLM/nucleus pull
request 35 nucleus's scripts/verify_kernel_requirement.py holds it against the
kconfig fragments of its realtime stream (D106,
the contract pair).
required-by is schema-generic (D103): an upper-case letter, then upper-case
letters, digits and hyphens, at most 128 bytes, with the bare REQ- refused.
That is exactly the form nucleus's ADR-0007 states, so this schema and nucleus's
verifier admit the same identifiers -- tools/test_payload_schemas.py compares
the published pattern with nucleus's own over every short value -- and imago's
flavour identifiers (FLAVOR-BASE, cordanaLLM/imago#48) decode under the schema
id they claim. The payloads Aegis itself issues stay on the REQ- form with a
row in docs/roadmap/requirements.md; tests/issued_payloads.rs holds every
required-by in build/ to that, and RequirementId::is_aegis_requirement is
the check.
architectures is all-of and fails closed (D104): a conforming kernel is built
for every listed architecture, and each build provides every feature. A
reference profile records one architecture, so the check against it reports each
other listed architecture as ArchitectureUnverified; a single profile proves a
requirement only when that requirement lists exactly the profile's architecture,
and a repeated architecture is one promise. Both reviewed kernel payloads list
x86-64 alone.
The published JSON Schemas (D105)¶
A consumer that is not written in Rust validates a payload against a JSON Schema, so both contracts are published as one, generated from the Rust types with schemars 1.2.2 and never written by hand:
build/kernel-requirement.schema.jsonforaegis.p01-nucleus.kernel-requirement.v1;build/product-input.schema.jsonforaegis.p01.product-input.v1.
Each is JSON Schema 2020-12. Every struct, at the top and nested, is type:
object with additionalProperties: false and its required fields; every
bounded field carries its pattern, anchored at both ends, and a maxLength
equal to the decoder's bound; and the list and range bounds are the decoder's
own constants (features 1 to 64, architectures 1 to 4, packages 1 to 64,
max-attempts 1 to 5, backoff-seconds 1 to 3600). The patterns use no
lookaround, so an ECMA-262 reader and Python's re read them alike. A consumer
can fetch a schema by its path at the Aegis commit it pins, as nucleus and imago
already fetch the payloads.
crates/aegis-fabrica-defs/tests/json_schema.rs fails when a committed file is
not byte for byte what the types generate and names the command that regenerates
both, AEGIS_WRITE_SCHEMAS=1 cargo test --locked -p aegis-fabrica-defs --test
json_schema. tools/test_payload_schemas.py reads them from the other side, as
a consumer does: the three payloads under build/ validate, the array form, an
unknown field and a value past a bound do not, and each pattern accepts and
refuses the values the crate's validators do.
What a JSON Schema cannot state stays the decoder's alone: a Kconfig symbol
required twice, a target release older than the minimum, and the other
invariants validate checks. A payload a schema accepts can therefore still be
refused by KernelRequirement::decode or ProductInputManifest::decode, and
never the reverse. cordanaLLM/praetor#607 records the fleet-level gap this
closes for one owner: no general way yet to check a consumer's decoder against
the schema owner's.
The fragment M26 consumes¶
KernelRequirement::config_fragment renders the payload as a Kconfig fragment:
one assignment per feature row, with the recorded requirement identifier on the
line above it, and no line that is neither a comment nor an assignment. It
validates before it renders and returns the refusal otherwise, because every
field of the payload is public and a caller that assembled one without decoding
it would otherwise receive a fragment silently short of the rows past the
feature bound -- and M26 applies this fragment to a base configuration.
# aegis.p01-nucleus.kernel-requirement.v1
# correlation-id: aegis-m18-kernel-requirement-0001
# minimum kernel release: 6.12
# REQ-P07-01
CONFIG_PREEMPT_RT=y
# REQ-P07-01
CONFIG_HZ_1000=y
# REQ-P07-06
CONFIG_SCHED_CLASS_EXT=y
built-in renders =y, module renders =m, absent renders
# CONFIG_X is not set, and present -- "either way" -- renders =y, because
a fragment states one assignment and built-in is the stronger of the two. That
is the form milestone M26 applies to a base configuration under decision D70.
What the host answered¶
Each row of a requirement names the probe that checks it, and every probe was run on the reference profile before the payloads were written. The commands and their output:
| Command | Output |
|---|---|
zgrep CONFIG_PREEMPT_RT /proc/config.gz |
# CONFIG_PREEMPT_RT is not set |
zgrep CONFIG_PREEMPT_DYNAMIC /proc/config.gz |
CONFIG_PREEMPT_DYNAMIC=y |
zgrep CONFIG_HZ_1000 /proc/config.gz |
CONFIG_HZ_1000=y |
zgrep CONFIG_SCHED_CLASS_EXT /proc/config.gz |
CONFIG_SCHED_CLASS_EXT=y |
zgrep CONFIG_BPF_LSM /proc/config.gz |
CONFIG_BPF_LSM=y |
zgrep '^CONFIG_DEBUG_INFO_BTF=' /proc/config.gz |
CONFIG_DEBUG_INFO_BTF=y |
zgrep '^CONFIG_INTEL_RAPL=' /proc/config.gz |
CONFIG_INTEL_RAPL=m |
cat /sys/kernel/security/lsm |
capability,landlock,lockdown,yama,bpf |
ls /sys/kernel/btf/vmlinux |
/sys/kernel/btf/vmlinux |
ls /sys/class/powercap |
intel-rapl, intel-rapl:0, intel-rapl:0:0 |
ls /sys/kernel/iommu_groups \| wc -l |
38 |
uname -r |
7.2.4-1-cachyos |
uname -m |
x86_64 |
zgrep '^CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=' /proc/config.gz |
CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y |
Those values are transcribed into planning/hardware-profile.json, which is
what the schema is checked against. The last row joined on 2026-09-29 with
D107's requirement row, and was read on the running 7.2.8-1-cachyos: the
7.2.4-1-cachyos kernel the other rows were read on is no longer installed,
and the profile's kernel.added_note says so.
The traceability is asserted rather than asserted-to. Each ProbeSource carries
the exact path it reads, and the test compares that path against the
evidence_command the profile records for the same capability, so a probe that
drifted away from the command the machine was measured with fails the gate
instead of reading plausibly.
Positive, negative and boundary on the measured profile¶
- Positive.
build/kernel-requirement.reference.jsonis satisfied byplanning/hardware-profile.jsonwith no unmet row: architecturex86-64, kernel release7.2.4-1-cachyos, the module ABI, thirteen Kconfig symbol states and the four runtime capabilities the probes read. - Negative, bound to a measured absence.
build/kernel-requirement.json-- the product requirement -- is refused by the same profile, with exactly one unmet row:CONFIG_PREEMPT_RTis required built-in and the profile records it as not set. This workstation runsPREEMPT_DYNAMIC, so it is not a kernel the product admits. That is the case the reference profile uniquely makes falsifiable: the refusal comes from a measured value, not from an invented one. - Boundary. A payload demanding only
CONFIG_HZ_1000is accepted by the very profile that refuses the product requirement, and relaxing only theCONFIG_PREEMPT_RTrow makes the product requirement pass with nothing else changed. The schema therefore discriminates per feature, not per kernel flavour.
The distribution ships a realtime kernel (extra/linux-rt), so the absence is
an operator action rather than a missing capability; docs/roadmap/hardware.md
records it as one, and D57 assigns the realtime work to a guest kernel at M23.
The mkosi gate¶
make verify-mkosi runs tools/verify_mkosi_definitions.py, which parses
build/mkosi.conf with the host's own mkosi. There is no || true anywhere in
it, and it guards itself twice before reporting anything: mkosi missing from
PATH, or a host below the admitted floor, prints why it did not run and
reports no pass.
Four cases, all observed on mkosi 27:
| Case | Command | Outcome |
|---|---|---|
| positive | mkosi --no-pager --directory build summary |
exit 0; the main image's Output stanza parses |
| negative | the same, on a scratch copy with MinimumVersion=28 |
exit 1, mkosi 28 or newer is required by this configuration (found 27) |
| negative | the same, on a scratch copy with one unknown setting appended | exit 0 with Unknown setting; the gate must reject it |
| boundary | the same, with MinimumVersion=27, the floor itself |
exit 0 |
mkosi exits 0 while it skips part of its input, so the exit code alone cannot
pass a case (E11-3, REQ-CI-01). mkosi 27.1 warns and carries on for an unknown
setting, a setting in the wrong section, a deprecated setting name and the
deprecated @ specifier (mkosi/config.py, parse_config_one); any of those
warnings fails a case that exited 0. The planted negative case is judged by
that same rejection and passes only when the rejection fires on mkosi's real
output, so a detector that stopped rejecting anything turns the gate red.
An unknown [Section] gets no warning at all: mkosi parses only the sections
its settings name and drops the rest. Before it looks for mkosi, the gate reads
the section headers of build/mkosi.conf and every build/mkosi.conf.d/*.conf
and fails on any name outside the sections mkosi 27.1 parses. That list is
pinned in the gate; tools/test_mkosi_definitions.py compares it with the
installed mkosi's own SETTINGS when mkosi is importable.
The floor cases run from a copy of build/ in a temporary directory with
exactly one line rewritten; a definition with no MinimumVersion= line, or with
more than one, fails the gate rather than silently running both cases at the
same version.
What the positive case reads back from the IMAGE: main stanzas:
Output Format: disk
Output: aegis-os.raw
Image ID: aegis-os
Distribution: arch
Architecture: x86-64
Snapshot: 2026/09/13
Repart Directories: <repository>/build/repart.d
The last line is the one that ties this milestone to M03: mkosi resolves
RepartDirectories=repart.d to the reviewed drop-in set, so the image
definition and the partition definitions are the same set of files rather than
two descriptions that happen to agree.
mkosi also reads a mkosi.repart/ directory beside the configuration if one
exists, and a mkosi.conf.d/ drop-in can add a further RepartDirectories=
row, so the resolved list is not necessarily the reviewed set alone. Neither of
those is a tracked file: git cannot record an empty directory, so a definition
tree can exist in a checkout, be read by mkosi and never appear in a diff. The
gate therefore requires two things rather than one, and the second is what keeps
the first honest: exactly one listed directory must be the reviewed one, and no
other listed directory may hold a partition definition. Identity is the resolved
path, not the end of the path, because a second set below another
build/repart.d is the case that matters. An empty extra directory is
tolerated; one holding a .conf fails the gate by name, and so does a second
row that resolves to the reviewed set itself, so a second definition set cannot
join the image without being seen.
mkosi summary resolves configuration and prints it. It downloads nothing and
builds nothing: unshare -rn mkosi --no-pager --directory build summary exits 0
with no network namespace at all, and the run leaves no file behind in the
repository.
Where the pins came from¶
Every mkosi setting name was read from mkosi documentation on the installed
mkosi 27, not from memory. Two values needed the installed version's own source
rather than its manual:
- the Arch snapshot identifier is formatted
%Y/%m/%dand is joined ontohttps://archive.archlinux.orgasrepos/<snapshot>/$repo/os/$arch; linux-rtis one of the names mkosi treats as a kernel package.
No mirror or archive was contacted to establish either. The snapshot is a configuration pin, and whether that snapshot builds is M11 work.
The package set is the one the recorded requirements name and nothing more:
linux-rt (REQ-P01-09, the D07 default), systemd (which ships
systemd-repart) and systemd-ukify (REQ-P02-06, which on this distribution is
its own package). The M01 register's remaining image packages row stays open
for M11; guessing a content set no requirement asks for would be the same kind
of unpinned inheritance this milestone exists to remove.
Scope limit¶
Two schemas, three reviewed payloads, one reviewed image definition and a
configuration parse. No image, UKI, kernel, artefact, signature or boot evidence
is produced or claimed. No producer repository is contacted, and neither the
Rust gate nor the mkosi gate needs a network: both were re-run inside an empty
network namespace and exited 0. make build, make boot and make release
remain blocked, and M09, M11 and M26 hold the evidence this page does not.