ontoref/reflection/constraints.ncl

52 lines
1.6 KiB
Plaintext
Raw Normal View History

2026-03-13 00:21:04 +00:00
# Validation contracts for reflection modes.
# These are applied AFTER the schema contract — they enforce semantic invariants
# that Nickel's structural typing cannot express alone.
# Pattern mirrors jpl_ontology/adrs/constraints.ncl: separate file, pure contracts.
let _non_empty_steps = std.contract.custom (
fun label =>
fun value =>
if std.array.length value.steps == 0 then
'Error {
message = "Mode '%{value.id}': steps must not be empty — a mode with no steps is passive documentation, not an executable contract"
}
else
'Ok value
) in
let _valid_trigger = std.contract.custom (
fun label =>
fun value =>
if std.string.length value.trigger == 0 then
'Error {
message = "Mode '%{value.id}': trigger must not be empty — it identifies how this mode is invoked"
}
else
'Ok value
) in
# Ensures every step that declares a cmd actually has a meaningful command (not whitespace-only).
let _non_empty_cmds = std.contract.custom (
fun label =>
fun value =>
let bad = value.steps
|> std.array.filter (fun s =>
std.record.has_field "cmd" s
&& std.string.length (std.string.trim s.cmd) == 0
)
|> std.array.map (fun s => s.id)
in
if std.array.length bad > 0 then
'Error {
message = "Mode '%{value.id}': steps with empty cmd: %{std.string.join ", " bad}"
}
else
'Ok value
) in
{
NonEmptySteps = _non_empty_steps,
ValidTrigger = _valid_trigger,
NonEmptyCmds = _non_empty_cmds,
}