feat: capture the whole wire surface, not just declarations - #3
Merged
Conversation
golden_tree.json holds $description documents, because conformance asks what a device declares. That cannot exercise a consumer: a parser fed only descriptions can be asked whether it understands the shape of a panel, never whether it builds the right snapshot from one, which is the part that reaches a user. golden_wire.json holds the retained surface instead -- descriptions, $state, and every property value, keyed the way a consumer receives them. 37 devices, 494 values. span-panel-api-schema-1 vendors it and drives its parser end to end from it, which is why it is an artifact rather than something internal. start_clone grows an optional publisher parameter, and the capture goes through it. Reassembling the emitter inside the script would make it a second copy of the production wiring, free to drift, and a capture taken through different wiring than a real panel uses proves less than it appears to. MqttPublisher was already a Protocol and CloneRuntime already held that type, so this is the seam being used rather than a new one. Values are not reproducible -- noise_factor perturbs power and current, and the clock advances -- so the test compares shape: which devices, which topics. A property added, removed or renamed fails it; a different wattage does not. That is the opposite policy to golden_report.json on purpose. A conformance profile must not drift silently, while a fixture of noisy floats held to byte equality would only produce failures nobody can act on. The capture lives in emitter_adapter rather than conformance: taking it means running this simulator, while the conformance rules describe any eBus publisher's output. test_boundary.py enforces that and caught the first placement.
check-spec-provenance.py cloned the specification on every run unless given --spec. It now defaults that flag from EBUS_SPEC_DIR, so a path set once in .env saves a network round trip per run. Purely an optimisation. Cloning still works and needs no setup, and a variable pointing at a directory that has since moved falls back to cloning rather than failing -- a stale path is the same situation as an unset one, and failing on it would punish someone for a line they wrote months ago. Same variable name as span-panel-api, whose schema_1 suite byte-compares its own vendored catalogs against a checkout. One name for one thing, so a single line in a shell serves both repositories.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
golden_tree.jsonholds$descriptiondocuments, because conformance asks what a device declares. That cannot exercise a consumer: a parser fed only descriptions can be asked whether it understands the shape of a panel, never whether it builds the right snapshot from one — which is the part that reaches a user.golden_wire.jsonholds the retained surface instead: descriptions,$state, and every property value, keyed as a consumer receives them.span-panel-api-schema-1vendors it and drives its parser end to end from it. That is why it is published as an artifact rather than kept internal.The capture goes through production wiring
start_clonegrows an optionalpublisherparameter, and the capture uses it. Reassembling the emitter inside the script would make it a second copy of that wiring, free to drift — and a capture taken through different wiring than a real panel uses proves less than it appears to.MqttPublisherwas already a Protocol andCloneRuntime.mqttalready held that type, so this is the existing seam being used rather than a new one. No broker is needed.Shape is asserted; values are not
noise_factorperturbs power and current and the clock advances, so values differ every run. The test compares which devices and which topics — a property added, removed or renamed fails it, a different wattage does not.That is the opposite policy to
golden_report.json, deliberately: a conformance profile must not drift silently, while a fixture of noisy floats held to byte equality produces only failures nobody can act on.Mutation-checked — injecting one phantom topic into the committed fixture fails the staleness guard by device id.
Placement
test_boundary.pycaught my first attempt, correctly. The capture lives inemitter_adapter, notconformance: taking it means running this simulator, while the conformance rules describe any eBus publisher's output. The test sits intests/conformance/because the fixture it guards does.Verified
pre-commit run --all-filesmypy --stricton the changed runtimeKnown gap, unchanged by this PR
grid/islanding-stateis still absent, because no config can produce a MID. The emitter supports one fully —wire/profiles/mid.jsondeclares thegridnode,bag_builderresolves("mid", "grid/islanding-state"),snapshotcarriesislanding_state, andEmitteriteratesof_class("mid")— butspec_generator.build_manifest()never creates a MIDDeviceInstance. So the gap is one missing instance in the manifest builder, not a config or capability gap.It is the single property
span-panel-api-schema-1reads that this simulator does not exercise, and it is the one whose mapping was corrected most recently (grid_statereadsislanding-state, notgrid-state). Worth closing, but it changes what the simulator publishes and would require recapturing both fixtures, so it belongs in its own PR.