0.3.0
Changed — behaviour you may be relying on
- A rule that compares against something nobody recorded is now
unknown, notfalse. A condition evaluates toTrue,FalseorUnknown, and a rule holds only whenTrue. The refusal is a newPreconditionUnobservable/InvariantUnobservablecarrying every address it could not read, sorted, rather than the first. Nobody looked and it is wrong used to be one message; sending an operator to fix a review that was never written is what that cost. existsis unchanged.Unknownis a property of the question, not of the operator asking it. Asking whether there is a value at an address is a question about the store, which the kernel can always answer — soexistsstays two-valued andnot: { exists: … }still means what it reads as. Only questions about a value —eq,ne,gt,gte,lt,lte,in,contains— can come backunknown, and only when there is no value to read. If a missing value should refuse plainly rather than stall the gate, guard the comparison in the same rule:all: [{exists: $fields.x}, {eq: [$fields.x, v]}], whichFalsedominance decides.- A key present with nothing after it is not a value.
review:with a blank after it is how YAML spells nobody filled this in, soexistsreportsfalsefor it and a comparison against it reportsunknown. Schema validation cannot catch this for ajson-kind field, wherenullis legal. Anullwritten as a literal in a definition is still a value. allandanyno longer short-circuit. Kleene's connectives are order-independent, so the answer is unchanged; what changes is that one refusal now names all three missing facts instead of three refusals naming one each. R-54's deterministic short-circuit clause was revised with the rest of the row, and the wording it replaced is quoted in the register.entity's JSON refusal gainsprecondition_unobservableandinvariant_unobservable, each with anunresolvedarray, and itsdefinitionrefusal gains adefectsarray beside the existingdefect. Exit codes are unchanged: a refusal is still1.CoreError::Definitionnow carriesDefinitionErrorsrather than oneDefinitionError, andRegistry::register/replace/EntityDefinition::validatereturn it. A caller that wants one defect reads.first().
Nothing about a lifecycle ladder changes. Every rule that never compares against a missing value evaluates exactly as it did — including both invariants in examples/order.yaml.
Added
- Registration reports every defect, not the first.
Registry::register,Registry::replaceandEntityDefinition::validatereturnDefinitionErrors— a non-empty list of typedDefinitionErrors — andentity validateprints them all, so fixing a definition takes one pass rather than one run per fault. Value validation has reported every failing field since 0.1.0 (R-23); this is the same for the definition itself. A check whose prerequisite already failed is skipped, so a lifecycle with a duplicate rung is one finding rather than one per transition it invalidates. Comparing aDefinitionErrorsto a singleDefinitionErrorholds only when it carries exactly that one, which is what keeps a single-defect assertion honest. Truth { True, False, Unknown }, public, with Kleeneand/or/notandis_satisfied. The variant names and tables are taken fromengineering-protocols' ownaep-domain::predicate::Truthrather than designed here — two kernels that disagreed about whatUnknownmeans would disagree about whether a gate passed.docs/requirements.mdgains R-57 (three-valued evaluation) and R-58 (which questions can beUnknownand which cannot), anddocs/design/kernel-v0.1.md§ 4.1 specifies both, including the rejected first draft that put the choice in the operator instead. R-50, R-51, R-53 and R-54 were revised; each replaced wording is quoted beneath its table.- The eight AEP lifecycles, as entity definitions.
examples/aep/*.yamlexpresses every lifecycle documentengineering-protocolsships —story,epic,initiative,task,design,specification,architecture-decision-record,review-result— as data this kernel executes, one operation per edge of each ladder. Phase 1 ofdocs/design/engineering-protocols-adoption-v0.1.md; no rules yet, because a precondition worth writing needs a rule that can sayunknown. - An equivalence test that makes the translation checkable, not asserted.
crates/entity-yaml/tests/aep_lifecycles.rscompares each definition's(from, to)edge set against the upstreamtransitionsmap, read from a committed fixture pinned at79b641c(crates/entity-yaml/tests/fixtures/aep-lifecycles/PIN.md) rather than from a sibling checkout. A definition that invents an edge and a ladder that grows one upstream both fail, by name. The gate runs it, andexample-checknow validatesexamples/aep/too. docs/roadmap.md— what order the adoption goes in, blocked on what, and the four decisions taken on 2026-08-25: phase 1 ships before phase 0 and is its evidence; the dependency arrow points fromengineering-protocolstoentity-coreand never back; a presentnullwill not count as a value; and an unobservable refusal will name every unresolved path.
Nothing in the kernel changed, and nothing here publishes a dependency in either direction.