Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"rulespec_commit": "6fe181fc4c65763150fe97501ef3443774e64869",
"engine": {
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": "04315d94d04efd61e27665ab3aa300d64fdf3a66"
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d"
}
},
"rulespec_pin": {
Expand Down Expand Up @@ -47,28 +47,28 @@
"A main benefit only where the rates module's income-tested net weekly payment for it is positive: entitlement treats the s 24 income test and the s 21 work gap as met (jobseeker_income_less_than_zero_rate_cutout true, jobseeker_in_full_time_employment false), and the Sole Parent Support and Supported Living Payment entitlement rules have no income term; the rates module tests the person's own income only."
]
},
"engine_optional_evidence": "The axiom-rules-engine commit named in input_surface.engine (the engine that generated the committed input surface) has no optional inputs. A RuleSpec module has no inputs section (RulesDocument holds format, imports, module, units, relations and rules; src/rulespec.rs:361-374), a rule-level default is rejected (RuleSpecError::UnsupportedDefault, src/rulespec.rs:91-94), and the dense batch call collects only the inputs the caller supplies (python-ext/src/lib.rs:458-466). A plain read of an absent input fails every row whose evaluation reaches it with EvalError::MissingInput (src/dense.rs:2790-2803), and that fails the request. The internal input_or_else node that would read a default (src/dense.rs:2805-2811) is never produced from RuleSpec text: formula lowering turns a name no rule defines into a plain input read (src/formula.rs:1400-1413). So no root input of these modules can be verified engine-optional, and this closure classes none as such: an input a run does not encode is defaulted here, with its reason.",
"engine_optional_evidence": "The axiom-rules-engine commit named in input_surface.engine (the engine that generated the committed input surface) gives these modules no optional inputs. A RuleSpec inputs block can't declare a default: the engine keeps it only to check only_related and selected-year reads and to read elections, and every other key stays inert (RulesDocument, src/rulespec.rs:663-687; declared_elections, src/rulespec.rs:1569-1611). No module the committed input surface covers has an inputs block, at the surface's RuleSpec commit or at the pin. The engine rejects a rule-level default (RuleSpecError::UnsupportedDefault, src/rulespec.rs:274-277), and the dense batch call collects only the inputs the caller supplies (python-ext/src/lib.rs:552-563). A plain read of an absent input fails every row whose evaluation reaches it with EvalError::MissingInput (src/dense.rs:5154-5168), and the first row error fails the whole request (src/dense.rs:1539, python-ext/src/lib.rs:404-409). The internal input_or_else node that would read a default (src/dense.rs:5169-5175) never comes from RuleSpec text: formula lowering turns a name that no parameter or rule defines into a plain input read (src/formula.rs:1833-1851). So no root input of these modules can be verified engine-optional, and this closure classes none as such: the closure defaults, with its reason, any input a run doesn't encode.",
"engine_source_pins": {
"note": "The axiom-rules-engine source files that engine_optional_evidence and undetermined_judgments cite by line, pinned at input_surface.engine.commit so each line citation names one exact file version.",
"pins": [
{
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": "04315d94d04efd61e27665ab3aa300d64fdf3a66",
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d",
"path": "python-ext/src/lib.rs"
},
{
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": "04315d94d04efd61e27665ab3aa300d64fdf3a66",
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d",
"path": "src/dense.rs"
},
{
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": "04315d94d04efd61e27665ab3aa300d64fdf3a66",
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d",
"path": "src/formula.rs"
},
{
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": "04315d94d04efd61e27665ab3aa300d64fdf3a66",
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d",
"path": "src/rulespec.rs"
}
]
Expand All @@ -80,7 +80,7 @@
"eligible"
],
"new_default": true,
"reason": "Entitlement judgments come back as codes 1 (holds), -1 (does not hold) and 0 (undetermined). At the axiom-rules-engine commit named in input_surface.engine, no dense leaf yields an undetermined value: dense input columns are booleans, integers, floats or text, an absent input is an error rather than an unknown, and and/or/not only pass an undetermined item along. With every root input encoded or defaulted by this closure, a 0 cannot arise, so the receipt kernel takeup.assign@1, once implemented, is to refuse any 0 it sees instead of guessing: a 0 would mean the engine or the closure changed. The alternatives a review could choose are treating 0 as not entitled (understates receipt) or as entitled (overstates it). microcosm.frame.input_closure.resolve_judgments implements all three."
"reason": "Entitlement judgments come back as codes 1 (holds), -1 (does not hold) and 0 (undetermined). At the axiom-rules-engine commit named in input_surface.engine, no dense leaf yields an undetermined value: dense input columns hold booleans, integers, decimals, floats, text or dates, an absent input is an error rather than an unknown, and and/or/not only pass an undetermined item along. With every root input encoded or defaulted by this closure, a 0 cannot arise, so the receipt kernel takeup.assign@1, once implemented, is to refuse any 0 it sees instead of guessing: a 0 would mean the engine or the closure changed. The alternatives a review could choose are treating 0 as not entitled (understates receipt) or as entitled (overstates it). microcosm.frame.input_closure.resolve_judgments implements all three."
},
"knobs": [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"version": 1,
"country": "nz",
"description": "Axiom rules bindings for the New Zealand v0 graph build. Each binding names one rulespec-nz module by repository-relative rulespec_path and SHA-256 at the pinned commit, the Frame entity it runs on, the period label it is evaluated for, and the variables the graph requests. No code interprets these bindings yet. The planned shared composer (package G6) will build one AxiomEngine per binding and register every binding in one simulate.rules_by_ref@1 kernel (to be added by package G1), because a run's kernel registry holds one kernel per ref. The Axiom adapter raises when a module declares relations; every bound module declares none at the pin. No kernel implementation hash is to bind the whole NZ spec.",
"description": "Axiom rules bindings for the New Zealand v0 graph build. Each binding names one rulespec-nz module by repository-relative rulespec_path and SHA-256 at the pinned commit, the Frame entity it runs on, the period label it runs for, and the variables the graph requests. build_transport_registry (transport/registry.py) builds one AxiomEngine per binding, and one simulate.rules_by_ref@1 router serves them all, because a run's kernel registry holds one kernel per ref. A binding that declares no relations goes through assert_no_relations, which refuses a module whose dense program on the binding's entity declares relations; every bound module declares none at the pin. No kernel implementation hash is to bind the whole NZ spec.",
"rulespec": {
"repository": "TheAxiomFoundation/rulespec-nz",
"commit": "8dc2507d5f10a0467c285fed54d018ee37246798",
Expand All @@ -12,9 +12,9 @@
},
"engine": {
"repository": "TheAxiomFoundation/axiom-rules-engine",
"commit": null,
"wheel_sha256": null,
"status": "awaiting the hub engine pin at M0 (D1 method card MC1); the composer (package G6, not yet written) is to refuse to run until both pins are set"
"commit": "753ce7d9ac4cfb161f6aa4f281f5a3980e67a57d",
"wheel_sha256": "054c9196d5371f13bb7c2b28f8161ac92294e5c8543aeb6e221fde243897ed42",
"status": "Pinned for G8-x (D1 method card MC1). commit is the axiom-rules-engine commit that NZ's input surface and input closure record. wheel_sha256 is the SHA-256 of the dense-extension wheel the NZ hub built at that commit (maturin build --release --locked; CPython 3.14, macOS arm64). axiom_engine_ref records both as declared and hashes no installed wheel."
},
"entity_names": {
"person": "Person",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"export_contract_sha256": "4d388236cc3d3382501f5cfae8eae44b661fba6919681965eaa3fe4e032efe86",
"axiom_input_closure_sha256": "e19c4f4e3903b1e23fdea46fe9227157d908c6ce943fbf53ee49531de9903714"
"axiom_input_closure_sha256": "acd994081b81c21f7b8da4ba80648d92cfd7ddd849db8bfa1db98c2fa2fe3b2e"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4564,7 +4564,13 @@
],
"encoding": "value"
},
"measurement": "ird_taxable_income_ty2027_v1",
"measurement": {
"resource": "income_measurements",
"path": [
"measurement_id"
],
"encoding": "value"
},
"identities": {
"resource": "income_measurements",
"path": [
Expand Down Expand Up @@ -4636,14 +4642,25 @@
],
"encoding": "value"
},
"measurement": "ird_taxable_income_ty2027_v1",
"measurement": {
"resource": "income_measurements",
"path": [
"measurement_id"
],
"encoding": "value"
},
"weight_entity": "household"
},
"artifact_inputs": [
{
"name": "problem",
"producer": "nz.targets.problem",
"artifact": "problem"
},
{
"name": "solution",
"producer": "nz.calibrate",
"artifact": "solution"
}
],
"artifact_outputs": [
Expand Down Expand Up @@ -4702,7 +4719,13 @@
"encoding": "json"
},
"weight_entity": "household",
"measurement": "ird_taxable_income_ty2027_v1"
"measurement": {
"resource": "income_measurements",
"path": [
"measurement_id"
],
"encoding": "value"
}
},
"artifact_inputs": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
size exactly as declared), beside the totals of what the measure does
include at the calibration problem's pre-fit weights. It adds no
model-side figure for an omitted item, because the register's sizes mean
IRD's.
IRD's. It decodes the fit's solution against that problem and refuses a
frame whose weights aren't the solution's, so the problem it reads is the
one this run's fit solved.
``transport.native_band_report@1``
Sets the model, at the run's pre-fit and post-fit weights, beside every
native row of the publisher's table: the row's count and amount from the
Expand All @@ -31,9 +33,14 @@
first-party module whose source its ``run()`` executes, and every first-party
module outside ``microcosm.graph`` whose constants it reads.
``microcosm.graph.kernel`` and ``microcosm.graph.decl`` stay out, as in the
evidence kernels' lists. The trace test checks the executed modules and can't
see a constant read, so a comment marks each entry whose module a run only
reads constants from.
evidence kernels' lists. Two tests check each list. A trace of one run checks
the executed modules, and can't see a constant read. A static walk of the
kernel's class and ``run()`` from source
(``test_support/microcosm_build/static_reach.py``) over-approximates what a run
can reach, in the ways that module's docstring lists. Outside
``microcosm.graph`` it must name exactly the listed modules, less one guarded
branch that no decoded target opens. A comment marks each entry whose module a
run only reads constants from.
``evidence_contract`` hashes only the kernels it lists, and this module stays
outside the gate battery's binding closure, so adding it re-keys no battery.

Expand Down Expand Up @@ -744,7 +751,7 @@ class OmissionRegisterKernel(KernelBase):
"measurement",
"weight_entity",
)
_inputs = MappingProxyType({"problem": PROBLEM_TYPE})
_inputs = MappingProxyType({"problem": PROBLEM_TYPE, "solution": SOLUTION_TYPE})

def implementation_hash(self) -> str:
return _implementation_hash(self.ref)
Expand Down Expand Up @@ -799,6 +806,29 @@ def run(self, context: KernelContext) -> KernelResult:
entity=entity,
weight_entity=weight_entity,
)
# Tie the problem to this run's fit: the solution must be the one
# solved from it, every weight unit on its axis must hold a record, and
# each record's frame weight (its weight unit's, which the frame
# broadcasts) must equal the solution's as a float64 value.
solution = decode_solution(
payloads["solution"],
problem_sha256=problem.sha256,
entity_ids=problem.entity_ids,
)
if len(set(rows)) != len(problem.entity_ids):
raise ValueError(
f"{ref} frame has a {weight_entity} on the problem's axis with no "
f"{entity}."
)
frame_weights = context.weights.get(entity)
if frame_weights is None or not np.array_equal(
np.asarray(frame_weights.values, dtype=np.float64),
np.asarray(solution.weights, dtype=np.float64)[rows],
):
raise ValueError(
f"{ref} frame {entity} weights differ from the calibration "
f"solution's {weight_entity} weights."
)
# The problem's initial weights are the fit's pre-fit weights, on its
# own entity axis; each record takes its weight unit's.
initial = problem.problem.initial_weights
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,21 @@ def test_every_default_is_cited_and_listed_for_review(self) -> None:
)

def test_no_input_is_claimed_engine_optional(self) -> None:
# The axiom-rules-engine commit in input_surface.engine (04315d94) has
# no optional inputs; the closure says why and classes none as
# engine-optional.
# The axiom-rules-engine commit in input_surface.engine (753ce7d9, the
# bindings' engine pin) gives the modules no optional inputs; the
# closure says why and classes none as engine-optional.
assert not any(
entry.closure_class is ClosureClass.ENGINE_OPTIONAL
for entry in CLOSURE.entries
)
assert "input_surface.engine" in CLOSURE.engine_optional_evidence
assert CLOSURE.surface_engine_commit.startswith("04315d94")
assert CLOSURE.surface_engine_commit.startswith("753ce7d9")
bindings = json.loads(
(CLOSURE_PATH.parent / "axiom_rules_bindings.json").read_text(
encoding="utf-8"
)
)
assert CLOSURE.surface_engine_commit == bindings["engine"]["commit"]
assert CLOSURE.surface_engine_repository == (
"TheAxiomFoundation/axiom-rules-engine"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -677,21 +677,20 @@ def test_nz_executable_support_keeps_region99_separate_from_ta999():


def _nz_composition_spec_and_config():
"""Supply compiler-only toy engine pins and CREATE's inventory.
"""Supply synthetic engine refs and CREATE's inventory.

The packaged graph declares every money producer, so the compiler checks
the complete packaged node and population structure with no synthetic
node. The toy pins certify no runtime result. CREATE's inventory holds
The packaged graph declares every money producer, and the packaged
bindings carry the engine pins, so the compiler checks the complete
packaged node and population structure with no synthetic node or pin. The
synthetic engine refs certify no runtime result. CREATE's inventory holds
canonical concepts and unit outputs only: no bridge or region column
enters as a root.
"""
from microcosm.graph import Owned

spec = _spec()
resources = spec["resources"]
resources["axiom_rules_bindings"]["engine"].update(
commit="1" * 40, wheel_sha256="2" * 64
)
compose.validate_transport_activation(spec)

rows = _instantiated_nz_rows(spec)
produced = {
Expand Down Expand Up @@ -1630,10 +1629,13 @@ def test_the_income_evidence_readers_name_the_measurement_but_dont_declare_it():

measurement_proofs counts every node upstream of a calibration that
declares the measurement's id and needs exactly one. The three readers
name the stand-in's id under ``measurement``, and the register reads
the problem's household weights. The readers sit two boundary filters
after the reweight, and only stand-ins write the three proxy columns, so
nz.terminal holds what nz.income.standin wrote at nz.central.
name the stand-in's id under ``measurement``, selected from
income_measurements' own ``measurement_id``. The stand-in keeps the literal
that R2C-1's proof reads, and the composed readers resolve to it. The
register reads the problem's household weights and checks the frame's
against the fit's solution. The readers sit two boundary filters after the
reweight, and only stand-ins write the three proxy columns, so nz.terminal
holds what nz.income.standin wrote at nz.central.
"""
spec = _spec()
rows = spec["resources"]["transport_graph"]["nodes"]
Expand All @@ -1643,13 +1645,21 @@ def test_the_income_evidence_readers_name_the_measurement_but_dont_declare_it():
]
assert declarers == ["nz.income.standin"]
by_id = {row["id"]: row for row in rows}
assert by_id["nz.income.standin"]["params"]["measurement_id"] == _MEASUREMENT
selection = {
"resource": "income_measurements",
"path": ["measurement_id"],
"encoding": "value",
}
_, _, graph, _ = _nz_composed()
for producer, _ in _INCOME_EVIDENCE.values():
assert by_id[producer]["params"]["measurement"] == _MEASUREMENT, producer
assert by_id[producer]["params"]["measurement"] == selection, producer
assert graph.node(producer).params["measurement"] == _MEASUREMENT, producer
register = by_id["nz.evidence.omission_register"]
assert register["params"]["weight_entity"] == "household"
assert [
(edge["name"], edge["producer"]) for edge in register["artifact_inputs"]
] == [("problem", "nz.targets.problem")]
] == [("problem", "nz.targets.problem"), ("solution", "nz.calibrate")]
for name, base in (("nz.as", "nz.calibrate"), ("nz.terminal", "nz.as")):
assert (by_id[name]["kernel"], by_id[name]["base"]) == (
"transport.boundary@1",
Expand Down
Loading
Loading