Skip to content

feat(binding): capture a revision against the binding in force - #53

Merged
AdeGneus merged 2 commits into
mainfrom
feat/binding-revision
Sep 23, 2026
Merged

AdeGneus merged 2 commits into
mainfrom
feat/binding-revision

Conversation

@AdeGneus

@AdeGneus AdeGneus commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

It completes the revision half of the commissioning ceremony. When a clamp is recalibrated, a contactor replaced or a polarity corrected, binding capture currently treats the site as new: it asks every question again and learns about fresh proof only from the runtime's refusal after the document is signed.

With this change, binding capture revises when the device holds a binding in force:

  • Where the prior comes from. The prior document comes from the runtime's commissioning binding-export. --revise <file> (or - for stdin) names a signed envelope for preparing a revision away from the device. Either prior must match the inventory's account of the binding in force, by binding_seq and canonical hash, before any question is asked.
  • What it asks. Each recorded fact is shown, and the installer is asked only whether it changed. Unchanged zones and fields are carried byte for byte. The prior value of anything changed goes into the draft's reason.
  • Naming fresh proof up front. Before any proof is asked for, capture names the fresh legs the contract requires. That is required for a change to a zone's sensor, actuator identity or mapping, for a rename onto another retained zone's name, and for a declared like-for-like replacement. A circuit leg not redone is recorded undemonstrated, and a control leg not redone is left absent. Neither is ever carried onto a changed zone.
  • Refused on a driven line. A revision that needs fresh legs on a line the binding in force still drives is refused as soon as the change is declared. The proof operation cannot claim a line an active zone owns, so a draft written for it could never be proven.
  • Checked before writing. The draft is checked against the retained state with the same revision rule the runtime applies, so a document the runtime would refuse is refused before it is written.

The Go verifier follows the restated rule in ori-platform/ori-specs#201:

  • every claimed leg is held to its own retained time;
  • a zone is matched by name, actuator or sensor;
  • a rename is held to the zone whose name it takes.

The corpus and the published misreading table are vendored under the manifest, and three tests guard them:

  • The adequacy test builds its rule and 98 misreadings from the table, and fails unless each misreading gets some vector wrong.
  • A seeded test compares the table's reference rule with the production verifier.
  • A new in-suite check holds every vendored file to its manifest digest.

The vendored files are pinned to ori-platform/ori-specs@7d9a6e8, the merge of ori-platform/ori-specs#201, and scripts/refresh-binding-vectors.sh reports them matching. This merges after ori-platform/ori-runtime#642.

Type of change

  • feat - new CLI command, output mode, bridge integration, or operator workflow
  • fix - bug fix or command behavior correction
  • docs - documentation only
  • test - tests only
  • refactor - no behavior change
  • security - touches tokens, deploy keys, credentials, or local runtime authority
  • contract-change - changes bridge, output JSON, cloud, Hub, or SDK contract usage

Required checklist

  • Linked issue is included below and acceptance criteria are addressed
  • go test ./... passes
  • go vet ./... passes
  • Pre-commit passes for changed files
  • Every new .go file has the Apache-2.0 license header
  • Command help text is updated for new or changed flags
  • JSON output is deterministic and tested when --output json is supported

CLI authority and safety checklist

  • Runtime-owned behavior delegates through the runtime bridge; the CLI does not parse ori.yaml independently
  • State queries go through the bridge; the CLI does not open runtime SQLite directly
  • Deploy keypairs are generated on-device; private keys never leave the device
  • Token commands never print raw token material except the explicit one-time generate result
  • Text output is operator-readable and error output is clear on stderr

External integration checklist

  • Timeout, malformed JSON, auth failure, and unavailable service paths are tested
  • Secrets and private keys never appear in logs or errors
  • Noninteractive behavior is documented and tested if added

If you used AI assistance

  • I can explain every line of AI-generated code in this PR
  • I have read and understood every file I modified
  • I am not submitting code I cannot defend in review

Related issue

Refs #34. This delivers the revision criteria: capture against the binding in force, fresh proof named before the installer begins, and tests for text and JSON output including every refusal. #34 stays open for profile activation, which waits on the runtime's safety-registry cutover.

Depends on ori-platform/ori-specs#201 (the contract) and on ori-platform/ori-runtime#642, which adds commissioning binding-export.

Testing notes

  • go vet ./... and gofmt are clean. go test -p 1 ./... passes. Under default parallelism internal/bridge can time out, which is internal/bridge fails under go test ./... but passes alone #46 and unrelated to this change.
  • The Go verifier accepts or rejects all 21 accept and 120 reject vectors at their declared stage and reason.
  • Each of the 98 misreadings gets exactly the same cases wrong as in the runtime's Python harness.
  • The Go verifier and the runtime's verifier agree on 60,000 seeded random revisions across all three postures.
  • The table and the manifest check were each shown to fail by editing the vendored files: an emptied table, a neutered misreading, an unknown switch, a wrong type, one byte changed, a stray file, and a missing file.

`binding capture` now revises when the device holds a binding in force. The
prior document comes from the runtime's `commissioning binding-export`, or
from a signed envelope named by --revise when preparing away from the device.
Either way it is bound to the inventory's account of the binding in force
before a question is asked. Each recorded fact is shown and the installer is
asked only whether it changed. Unchanged zones and fields are carried byte for
byte, and the prior value of anything changed is carried into the draft's
reason.

Before any proof is asked for, capture names the fresh legs the contract
requires: for a change to a zone's sensor, its actuator identity or its
mapping, for a rename onto another retained zone's name, or for a declared
like-for-like replacement. A leg not redone is recorded undemonstrated (the
circuit leg) or left absent (the control leg), never carried onto a changed
zone. A revision needing fresh legs on a line the binding in force still
drives is refused as soon as the change is declared, since the proof operation
cannot claim a line an active zone owns. The draft is checked against the
retained state with the same revision rule the runtime applies, so a document
the runtime would refuse is refused before it is written.

The Go verifier follows the restated rule: every claimed leg is held to its own
retained time, zones are matched by name, actuator or sensor, and a rename is
held to the zone whose name it takes. The corpus and the published misreading
table are vendored under the manifest. The adequacy test builds its rule and
misreadings from that table and fails unless every misreading gets some vector
wrong. A seeded test compares the table's reference rule with the production
verifier, and an in-suite check holds every vendored file to its manifest
digest.
@AdeGneus
AdeGneus marked this pull request as ready for review September 23, 2026 02:42
@AdeGneus AdeGneus self-assigned this Sep 23, 2026
@AdeGneus
AdeGneus merged commit 3b02557 into main Sep 23, 2026
1 check passed
@AdeGneus
AdeGneus deleted the feat/binding-revision branch September 23, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant